Repository navigation
fix(genui): stop GenUI reminder from triggering an extra turn - #56548
Open
kitlangton wants to merge 2 commits into
Open
kitlangton wants to merge 2 commits into
kitlangton wants to merge 2 commits into
Conversation
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
In the OpenCode app, sending a first prompt with Generated UI enabled (
metadata.genui = true) caused the assistant to reply twice to a single prompt. In sessionses_eb4805506ffdExxfN3euuGJzSR, the user sent"hi"at1792268020571, assistant 1 completed at1792268021922, asyntheticmessage containing the GenUI<system-reminder>was delivered at1792268021930, and assistant 2 started at1792268021939and greeted the user a second time.GenUISession'scontexthook both splicedMessage.user(enter)intoevent.messagesand calledctx.session.synthetic({ sessionID, text, resume: false }). BecauseSession.syntheticadmits a"steer"inbox item andresume: falseonly skipsexecution.wake(), the already-active runner promoted the synthetic item inadvanceToStepafter step 1 finished, running an extra model step and persisting the reminder after the assistant's reply (diverging from the order the model saw).What Changes
Make the
GenUISessioncontexthook side-effect free by providing GenUI guidance as a system part (event.system.push(SystemPart.make(...))), matchingpackages/core/src/tool/plugin/opencode.ts:contexthook call,GenUISessionscannedevent.messagesforenter/leave<system-reminder>user messages, spliced a user message intoevent.messages, and calledctx.session.synthetic({ sessionID, text, resume: false }), queuing a steered synthetic message while a step was already running.prompthook records whether the latest prompt hadmetadata.genui === true(andsession.deletedcleans up the map entry). Oncontext, if GenUI is offered for the session,GenUISessionpushesSystemPart.make(instructions)ontoevent.systemand keepsgenui_render; otherwise it deletesgenui_renderfromevent.toolsand adds nothing. It never mutatesevent.messagesor enqueues synthetic inbox items.sequenceDiagram participant A as App participant R as Runner participant H as GenUISession (context hook) participant M as Model Note over A,M: Before A->>R: prompt ("hi", metadata.genui = true) R->>H: context hook H->>R: splice Message.user(enter) + session.synthetic(resume: false) R->>M: step 1 (user: enter, user: "hi") M-->>R: greeting 1 R->>R: advanceToStep promotes steered synthetic item R->>M: step 2 (persisted synthetic reminder) M-->>A: greeting 2 Note over A,M: After A->>R: prompt ("hi", metadata.genui = true) R->>H: context hook H->>R: event.system.push(SystemPart.make(instructions)) R->>M: step 1 (system includes GenUI guidance, user: "hi") M-->>A: greeting 1Scope
This PR keeps the fix small in
packages/plugin-genui/src/session.tsusing existingevent.systemsupport on thecontexthook, plus unit and runner regression tests. A separate follow-up PR will move GenUI onto keyed session instruction entries once plugins can access them.Verification
4 pass, 0 fail. Cover enabled guidance on repeated context calls, disabled and missing metadata, and unknown sessions; messages remain unchanged and no synthetics are enqueued.1 pass, 0 fail. Uses the real tool registry and verifies exactly one model request, with bothgenui_renderand its system guidance, only"hi"in user input, and no persisted reminder.