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
Server plugins can't reply to forms (ctx.session.form is missing) #51108
In OpenCode 2.0.16, the server plugin context exposes ctx.permission.reply but no way to reply to a form. SessionDomain in @opencode/plugin/dist/promise/session.d.ts picks create, get, switchAgent, switchModel, prompt, generate, command, synthetic, interrupt, update, move, wait and context from SessionApi, and form isn't in that list. The runtime object matches: Object.keys(ctx.session) has no form.
This blocks server plugins that answer questions from an external system. opencode-homeassistant polls a Home Assistant entity and replies to permission requests and question tool forms. Permissions work through ctx.permission.reply. For forms, the plugin has to emit an RPC event that a companion CLI plugin turns into context.data.session.form.reply, so remote answers only work while a terminal interface is connected.
Could SessionDomain include form (at least list, get, reply and cancel)?
Two small docs mismatches I found while porting:
The plugins guide uses ctx.session.rename, but the context only has ctx.session.update({ sessionID, title }).
The same guide calls ctx.permission.reply({ sessionID, requestID, reply: 'once' }), but PermissionReplyInput expects decision.
I'll take this. Confirmed against current v2: SessionDomain in packages/plugin/src/promise/session.ts (and its Effect counterpart) picks a fixed key list from SessionApi and form isn't in it, while the runtime ctx.session mirrors that projection — so a server plugin has no form surface at all.
Plan: add form to SessionDomain for both the Promise and Effect plugin surfaces, bridging list/get/reply/cancel through the existing host like permission.reply does. Also fix the two docs mismatches you flagged (ctx.session.rename → ctx.session.update, reply → decision).
Clawd on Desk is a desktop companion for coding agents. For OpenCode, it already shows permission prompts as desktop cards through ctx.permission.hook("evaluate") on 2.x. On 1.x, we're adding direct answers to question tool prompts through the native question API (rullerzhou-afk/clawd-on-desk#1163).
On 2.x, the question tool goes through Form, but the plugin context has no form surface, so there is no supported way to provide the same experience. The only workaround would be calling the service HTTP API from outside the plugin, which means discovering its URL and credentials. We'd rather not ship that.
The ctx.session.form.list/get/reply/cancel API proposed in #51142 is exactly what we'd build on. We don't need plugins to create forms; listing, inspecting, replying to, and cancelling pending forms is sufficient for this use case.
One related question: are form.created, form.replied, and form.cancelled delivered to server plugins through ctx.event.subscribe()? If so, a plugin can react when a form appears instead of polling ctx.session.form.list.
Downstream use case +1. opencode-telegram-notify (npm: @hamzaabili/opencode-telegram-notify) sends permission requests and question forms to a Telegram chat. Permission taps work today through ctx.permission.reply({decision}) (verified on 2.1.4). Question taps are implemented behind a surface probe (ctx.session.form.reply / ctx.question.reply) but on 2.1.x the plugin context exposes neither, so forms fall back to notify-only with a terminal pointer — no dead buttons. form.created/replied/cancelled do arrive via ctx.event.subscribe(), so the plugin can react to forms; it only lacks the reply/cancel surface to answer them. Once ctx.session.form (or an equivalent question surface) lands, the plugin enables in-chat answers automatically with no further changes on our side. Happy to test against a dev build if useful.
You're asking for server plugins to be able to answer forms. That means exposing ctx.session.form (list, get, reply, cancel) the same way ctx.permission.reply already works for permissions. Then integrations like opencode-homeassistant could answer question forms without a connected terminal interface. I checked the current code: SessionDomain still doesn't include form, though the HTTP API and client already support these operations. The maintainers will consider it.
On the two docs mismatches: the current plugins guide source already uses ctx.session.update({ sessionID, title }) and ctx.permission.reply({ sessionID, requestID, decision: "once" }), so the published page should show those once it's updated.
Activity
holny commentedon Sep 24, 2026
I'll take this. Confirmed against current v2:
SessionDomaininpackages/plugin/src/promise/session.ts(and its Effect counterpart) picks a fixed key list fromSessionApiandformisn't in it, while the runtimectx.sessionmirrors that projection — so a server plugin has no form surface at all.Plan: add
formtoSessionDomainfor both the Promise and Effect plugin surfaces, bridginglist/get/reply/cancelthrough the existing host likepermission.replydoes. Also fix the two docs mismatches you flagged (ctx.session.rename→ctx.session.update,reply→decision).Fixes #51108
fix(plugin): expose session forms to server plugins
fix(plugin): expose session forms to server plugins
rullerzhou-afk commentedon Oct 9, 2026
Adding a concrete downstream use case for this.
Clawd on Desk is a desktop companion for coding agents. For OpenCode, it already shows permission prompts as desktop cards through
ctx.permission.hook("evaluate")on 2.x. On 1.x, we're adding direct answers toquestiontool prompts through the native question API (rullerzhou-afk/clawd-on-desk#1163).On 2.x, the question tool goes through
Form, but the plugin context has no form surface, so there is no supported way to provide the same experience. The only workaround would be calling the service HTTP API from outside the plugin, which means discovering its URL and credentials. We'd rather not ship that.The
ctx.session.form.list/get/reply/cancelAPI proposed in #51142 is exactly what we'd build on. We don't need plugins to create forms; listing, inspecting, replying to, and cancelling pending forms is sufficient for this use case.One related question: are
form.created,form.replied, andform.cancelleddelivered to server plugins throughctx.event.subscribe()? If so, a plugin can react when a form appears instead of pollingctx.session.form.list.fix(plugin): expose session forms to server plugins
Hamza-Abili commentedon Oct 26, 2026
Downstream use case +1. opencode-telegram-notify (npm: @hamzaabili/opencode-telegram-notify) sends permission requests and question forms to a Telegram chat. Permission taps work today through
ctx.permission.reply({decision})(verified on 2.1.4). Question taps are implemented behind a surface probe (ctx.session.form.reply/ctx.question.reply) but on 2.1.x the plugin context exposes neither, so forms fall back to notify-only with a terminal pointer — no dead buttons.form.created/replied/cancelleddo arrive viactx.event.subscribe(), so the plugin can react to forms; it only lacks the reply/cancel surface to answer them. Oncectx.session.form(or an equivalent question surface) lands, the plugin enables in-chat answers automatically with no further changes on our side. Happy to test against a dev build if useful.opencodejr commentedon Oct 28, 2026
Thanks for the detailed write-up, @pedropombeiro!
You're asking for server plugins to be able to answer forms. That means exposing
ctx.session.form(list,get,reply,cancel) the same wayctx.permission.replyalready works for permissions. Then integrations like opencode-homeassistant could answerquestionforms without a connected terminal interface. I checked the current code:SessionDomainstill doesn't includeform, though the HTTP API and client already support these operations. The maintainers will consider it.Related:
formsurface to the Promise and Effect plugin session domains (a port of fix(plugin): expose session forms to server plugins #51142)On the two docs mismatches: the current plugins guide source already uses
ctx.session.update({ sessionID, title })andctx.permission.reply({ sessionID, requestID, decision: "once" }), so the published page should show those once it's updated.fix(plugin): expose session form replies to server plugins
fix(plugin): expose session form replies to server plugins
fix(plugin): expose session form replies to server plugins
fix(plugin): expose session form replies to server plugins
fix(plugin): expose session form replies to server plugins