feat: interactive question cards (AskUserQuestion → tappable Buzz cards) - #5120
Open
billy-mangaroa wants to merge 4 commits into
Open
feat: interactive question cards (AskUserQuestion → tappable Buzz cards)#5120billy-mangaroa wants to merge 4 commits into
billy-mangaroa wants to merge 4 commits into
Conversation
…able Buzz cards) Render Claude Code's AskUserQuestion (used by /interview) as tappable cards inside a Buzz channel instead of degrading to prose. AskUserQuestion surfaces over ACP as an `elicitation/create` form (gated on the client advertising the `elicitation.form` capability); this lands the foundation + card renderer + the pure bridge core, ahead of wiring the async round-trip in buzz-acp/pool. - buzz-core: KIND_ELICITATION_REQUEST (44300, agent's question card) and KIND_ELICITATION_RESPONSE (44301, member's answer); registered in ALL_KINDS. - buzz-relay: both kinds scoped as MessagesWrite in required_scope_for_kind. - buzz-acp: pure, tested helpers (elicitation.rs) — parse a request's requestedSchema into per-question cards, parse an answer event, and fold answers back into the ACP CreateElicitationResponse keyed by the schema's question_<n>/question_<n>_custom properties (custom answer wins). - desktop: QuestionCard renderer (owner-locked, single/multi-select, "Other…" free-text, answered-state via live subscription) wired into MessageRow; publishes a 44301 answer referencing the card. Remaining (next commit): advertise elicitation.form in buzz-acp initialize, dispatch `elicitation/create`, and add the pool servicer that publishes one card per question and awaits the owner's answer. See PLANS/INTERACTIVE_QUESTION_CARDS.md in the Buzz workspace. Signed-off-by: Billy Lewis <hello@billylewis.co.nz> Co-authored-by: Billy Lewis <hello@billylewis.co.nz>
Completes the interactive question-card feature: AskUserQuestion now
publishes tappable cards to the channel and feeds the owner's tap back to
the running skill.
- acp.rs: advertise `elicitation: { form: {} }` in initialize (this is the
switch claude-agent-acp gates AskUserQuestion on); dispatch inbound
`elicitation/create` in the prompt read loop via `service_elicitation`,
which hands the parsed form to the pool servicer and writes the ACP
`CreateElicitationResponse`. The wait for a tap is legitimately long
(no timeout), so the loop is parked on the await — no idle/hard-deadline
can fire mid-wait — and both deadlines are renewed afterwards.
- pool.rs: `ElicitationBridgeRequest` + a per-turn `run_elicitation_servicer`
(installed only for channel turns with a known owner). It publishes one
owner-locked 44300 card per question, polls the relay for the owner's
44301 answers, and folds them into the ACP response. `AbortOnDrop` tears
the servicer down on every turn-exit path; `send_prompt_result` clears the
installed sender so heartbeats never inherit it.
- Fail-closed: no servicer / non-form / no questions / cancelled turn all
answer `{ "action": "cancel" }`, so the model cleanly falls back to prose.
Tests: capability advertised; existing initialize-format test still green;
the pure parse/fold helpers covered by 11 unit tests. clippy clean.
Signed-off-by: Billy Lewis <hello@billylewis.co.nz>
Co-authored-by: Billy Lewis <hello@billylewis.co.nz>
Proves against a running relay what the unit tests cannot: the relay accepts kind:44300 (card) and kind:44301 (answer), and the owner's answer is retrievable via the exact filter the buzz-acp servicer polls with (kind:44301 & author & #e=<card id>). Both tests green against the branch relay (BUZZ_GIT_CONFORMANCE_PROBE=false, seeded localhost community). Marked #[ignore] like the other e2e_* suites; run with: RELAY_URL=ws://localhost:3001 cargo test --test e2e_elicitation -- --ignored Signed-off-by: Billy Lewis <hello@billylewis.co.nz> Co-authored-by: Billy Lewis <hello@billylewis.co.nz>
Renders a 44300 card owner-locked to the mock identity and asserts the open/interactive state (options as buttons + Other free-text), capturing test-results/screenshots/question-card.png. Registered in the smoke project. Signed-off-by: Billy Lewis <hello@billylewis.co.nz> Co-authored-by: Billy Lewis <hello@billylewis.co.nz>
billy-mangaroa
force-pushed
the
feat/interactive-question-cards
branch
from
August 6, 2026 23:00
34f9707 to
4f9ddce
Compare
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.
Interactive question cards (AskUserQuestion → tappable Buzz cards)
Renders Claude Code's built-in AskUserQuestion tool (used by discovery skills like
/interview) as tappable cards inside a Buzz channel, instead of silently degrading to prose.Why it was broken
AskUserQuestion surfaces over ACP as an
elicitation/createform (mode:"form").claude-agent-acponly enables the tool when the client advertises theelicitation.formcapability;buzz-acpadvertised none, so the adapter dropped AskUserQuestion intodisallowedToolsand the model fell back to prose.What this does
buzz-acpadvertiseselicitation: { form: {} }ininitialize, dispatches inboundelicitation/create, and (in the pool) stands up a per-turn servicer that publishes one owner-locked question card per question, polls for the human's answer, and folds it back into the ACPCreateElicitationResponse. The wait is unbounded (the loop parks on the await, so idle/hard deadlines can't fire mid-wait; both are renewed after).buzz-coreaddsKIND_ELICITATION_REQUEST = 44300(card) andKIND_ELICITATION_RESPONSE = 44301(answer);buzz-relayscopes both asMessagesWrite.QuestionCardrenderer (owner-locked, single/multi-select, "Other…" free-text, answered-state via live subscription).Fail-closed throughout: no servicer / non-form / no questions / cancelled turn all answer
{ "action": "cancel" }, so the model cleanly falls back.Tests
e2e_elicitation.rs): the relay accepts both kinds and the owner's answer is retrievable via the exact filter the servicer polls with.clippyclean;cargo fmtapplied.🤖 Generated with Claude Code