You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE]: Plugin UI surface for the web/desktop app, mirroring the TUI plugin API #43132
V2 has a rich client-side plugin surface for the TUI (@opencode-ai/plugin/tui): Plugin.define({ id, setup }) with keymap.layer (slash commands with run(input) that execute before any model dispatch), ui.dialog (show/select/prompt/confirm), ui.slot (e.g. sidebar.content), host-reactive storage, and client. The loader resolves package plugins through the exports["./tui"] subpath.
The web/desktop app (packages/app) has no equivalent: no plugin runtime, no slot system, no client-side command hooks. The Extensions settings tab (packages/app/src/components/settings-v2/extensions.tsx) is a read-only list of server config. So any plugin UX built for the TUI simply cannot exist in the web or desktop clients.
Concrete use case
opencode-waits schedules a prompt for later (/wait 1hour implement this). Its whole point is working while rate limited, so scheduling must not cost a model turn. In the TUI that works: a plugin slash command intercepts before submission, plus a sidebar section with live countdowns and a manager dialog (list/search/add/edit/delete/send-now) built from ui.dialog.show + keymap layers.
In the web/desktop app the only available fallback is a server-side command template that instructs the agent to call a scheduling tool — one model turn per use, and unusable exactly when rate limited, which is the scenario the feature exists for.
The ask
A plugin UI surface for packages/app, even a small subset of the TUI's:
Client-side slash commands with a run(input) executed before prompt submission — this alone unlocks model-free plugin commands.
Dialogs (the select/prompt/confirm primitives).
A sidebar slot.
A natural shape would be an exports["./web"] entrypoint mirroring the ./tui loader convention, with a context exposing the same client/storage/ui capabilities adapted to solid-on-DOM.
I understand the V2 plugin API is beta and moving; happy to adapt the plugin to whatever shape this takes, and to test early iterations.
Reacted by Lennart Weller, Kamil Chmielewski, VladimirLevadnij, Zheyuan Wu, Lukas Kreussel, Shawn Kiser, Michael Lindblom, Etsuya Akimoto, amnesia, 邓超 and 7 more
[FEATURE]: Generic UI Intent Channel for cross-client plugin-driven UX #6330: [FEATURE]: Generic UI Intent Channel for cross-client plugin-driven UX — proposes a declarative UI intent system for plugins across all clients (TUI, Desktop, Web), overlapping with the dialog/sidebar primitives requested here
+1 from opencode-quota (~980 stars). In the TUI we show quota in a sidebar slot and toasts. On Web/Desktop plugins get no slot and no toast. Even just the sidebar slot and toasts on the web app would cover us.
+1, with a different use case and a checkout-level map of what exists today, in case it helps scope the first cut.
We're porting a set of plugins to 2.x that have to behave the same in the TUI, the web app and desktop. Mapping 2.0.31 (83c5abbbe3):
The only client plugin surface is packages/plugin/src/tui/ (exports["./tui"], packages/plugin/package.json:12-18): slots (context.ts:180-203), dialogs (:327-405), keymap slash commands (:407-433), toasts (:490-542).
packages/app has no plugin runtime, slot, dialog or command hook at all. Desktop renders the same app, so it inherits the gap.
What every client does render for a plugin: session messages, host-native permission asks, and the question tool. That's the whole cross-client surface right now, and none of it can run plugin code before a model turn or collect input outside the transcript.
Two things we need that the fallback (a command template plus a tool) can't do:
An operator action that must not be a model turn and must not land in the transcript. Ours is a sudo askpass flow: the plugin rewrites the shell call to sudo -A, the askpass helper asks the operator for the password, the password goes straight to sudo. In the TUI we built the modal ourselves. On web/desktop there is no plugin path to ask the operator anything, so the feature has to refuse instead of prompt.
An operator broadcast to other sessions on the same service (explicit message or a scheduled reminder). Again a plugin command with run(input) plus a small form, nothing a model should be involved in.
Minimum subset that would cover both, in order: slash commands with run(input) executed before submission (already the top ask here), dialog.prompt and dialog.confirm, a toast. A sidebar slot would be nice but neither of the above needs it. We'd test an early iteration.
The web app (opencode serve / opencode web) still loads no third-party GUIs: createWebPlatform sets no plugins bridge, so PluginGuiLoader never mounts (packages/app/src/runtime/plugin/root.tsx). The SDK already has ctx.app.platform: "web", so it looks designed for it.
Is web still planned under this issue? If so, a possible approach:
Load GUIs only from the server that served the page (same origin, which already runs the plugins' server parts). Never in the hosted app.
Serve each bundle as a same-origin script (e.g. /api/plugin/gui/<id>.js), since the web CSP (script-src 'self') blocks the new Function evaluation desktop uses.
Ipc, embeds and menubar stay inactive, as the docs already describe for web.
Use case: I embed the OpenCode web UI in a dashboard and drive its tabs, keys and composer through internals today. ctx.tabs, Command and composer would replace all of that.
Thanks for the detailed write-up and the concrete use case. You're asking for a plugin UI surface in the web/desktop app with client-side slash commands, dialogs and a sidebar slot, so plugins like opencode-waits work there without spending a model turn.
Much of this already exists for OpenCode Desktop as an experimental GUI plugin API (@opencode/plugin/gui, available since v2.2.0). A package exposes it through a ./gui-experimental export next to ./tui and is listed in opencode.json(c) like any other plugin. The docs are under Build › Plugins › GUI (source: services/www/src/docs/content/build/plugins/gui/index.mdx). It includes:
Slash commands:ctx.add(Command, () => ({ id, title, slash: { name: "wait", arguments: true }, run: (input) => ... })). With arguments: true, the text after /wait is passed to run(input).
Dialogs:ctx.dialogs.confirm/alert/prompt/select use the app's own dialogs, and ctx.dialogs.open(render) with ctx.ui.Dialog lets you build a custom manager dialog.
UI surfaces:Panel tabs in the session side region (a good place for a live countdown list), plus Slots at window.bottom, session.header, session.location.menu and session.panel.end.
Data and feedback:ctx.client, stores and toasts.
What isn't there yet, as far as I can see:
A plain browser tab doesn't load third-party GUI plugins; only the desktop app does.
There is no dedicated session-sidebar slot like the TUI's sidebar.content.
The API is experimental, so its shape may still change. It would help if you could try porting opencode-waits to ./gui-experimental on Desktop. Please confirm here whether a slash Command stays fully client-side for your case, and say which remaining gaps block you.
Activity
github-actions commentedon Aug 17, 2026
This issue might be a duplicate of existing issues. Please check:
slkiser commentedon Sep 26, 2026
+1 from opencode-quota (~980 stars). In the TUI we show quota in a sidebar slot and toasts. On Web/Desktop plugins get no slot and no toast. Even just the sidebar slot and toasts on the web app would cover us.
iceteaSA commentedon Oct 18, 2026
+1, with a different use case and a checkout-level map of what exists today, in case it helps scope the first cut.
We're porting a set of plugins to 2.x that have to behave the same in the TUI, the web app and desktop. Mapping 2.0.31 (
83c5abbbe3):packages/plugin/src/tui/(exports["./tui"],packages/plugin/package.json:12-18): slots (context.ts:180-203), dialogs (:327-405), keymap slash commands (:407-433), toasts (:490-542).packages/apphas no plugin runtime, slot, dialog or command hook at all. Desktop renders the same app, so it inherits the gap.questiontool. That's the whole cross-client surface right now, and none of it can run plugin code before a model turn or collect input outside the transcript.Two things we need that the fallback (a command template plus a tool) can't do:
sudoaskpass flow: the plugin rewrites the shell call tosudo -A, the askpass helper asks the operator for the password, the password goes straight to sudo. In the TUI we built the modal ourselves. On web/desktop there is no plugin path to ask the operator anything, so the feature has to refuse instead of prompt.run(input)plus a small form, nothing a model should be involved in.Minimum subset that would cover both, in order: slash commands with
run(input)executed before submission (already the top ask here),dialog.promptanddialog.confirm, a toast. A sidebar slot would be nice but neither of the above needs it. We'd test an early iteration.magoz commentedon Oct 27, 2026
Desktop GUI plugins shipped in v2.2.0 (#55246, #59469), announced here: https://x.com/LukeParkerDev/status/2115009131535085782. That covers the desktop half of this issue.
The web app (
opencode serve/opencode web) still loads no third-party GUIs:createWebPlatformsets nopluginsbridge, soPluginGuiLoadernever mounts (packages/app/src/runtime/plugin/root.tsx). The SDK already hasctx.app.platform: "web", so it looks designed for it.Is web still planned under this issue? If so, a possible approach:
/api/plugin/gui/<id>.js), since the web CSP (script-src 'self') blocks thenew Functionevaluation desktop uses.inactive, as the docs already describe for web.Use case: I embed the OpenCode web UI in a dashboard and drive its tabs, keys and composer through internals today.
ctx.tabs,Commandandcomposerwould replace all of that.Happy to open a PR if the approach fits.
Hona commentedon Oct 27, 2026
Yeah so 'remote servers' are on the list to figure out
Which sorta means for web too :)
magoz commentedon Oct 28, 2026
Makes sense, thanks! Are you open to contributions? Happy to help :-)
opencodejr commentedon Oct 28, 2026
Thanks for the detailed write-up and the concrete use case. You're asking for a plugin UI surface in the web/desktop app with client-side slash commands, dialogs and a sidebar slot, so plugins like opencode-waits work there without spending a model turn.
Much of this already exists for OpenCode Desktop as an experimental GUI plugin API (
@opencode/plugin/gui, available since v2.2.0). A package exposes it through a./gui-experimentalexport next to./tuiand is listed inopencode.json(c)like any other plugin. The docs are under Build › Plugins › GUI (source:services/www/src/docs/content/build/plugins/gui/index.mdx). It includes:ctx.add(Command, () => ({ id, title, slash: { name: "wait", arguments: true }, run: (input) => ... })). Witharguments: true, the text after/waitis passed torun(input).ctx.dialogs.confirm/alert/prompt/selectuse the app's own dialogs, andctx.dialogs.open(render)withctx.ui.Dialoglets you build a custom manager dialog.Paneltabs in the session side region (a good place for a live countdown list), plusSlots atwindow.bottom,session.header,session.location.menuandsession.panel.end.ctx.client, stores and toasts.What isn't there yet, as far as I can see:
sidebar.content.The API is experimental, so its shape may still change. It would help if you could try porting opencode-waits to
./gui-experimentalon Desktop. Please confirm here whether a slashCommandstays fully client-side for your case, and say which remaining gaps block you.I've noted this for the maintainers to consider.