Summary
Over the pi-acp bridge, only single-choice ask_user questions reach the phone. Multi-select and free-text questions produce no client request at all — the agent is immediately told "User cancelled the question" and continues on its own assumption. The user never learns they were asked.
Pre-existing; not introduced by #104 (which fixed the single-choice and yes/no journeys). makit's own ask card already supports both shapes — the Codex adapter passes multiSelect, and the card has a free-text handoff — so only the pi route is starved.
Evidence
Recorded against real pi via server/test/record-acp-session.ts. Counts are client requests actually observed on the ACP connection:
| Prompt to the agent |
requestPermission |
createElicitation |
what the agent saw |
| single choice ("TypeScript or Python?") |
1 |
0 |
the user's pick ✅ |
allowMultiple: true ("which fruits?") |
0 |
0 |
cancelled: true ❌ |
allowFreeform: true, no options ("favourite city?") |
0 |
0 |
cancelled: true ❌ |
single choice, user taps ✏️ Type custom response... |
1 |
0 |
cancelled: true ❌ |
Final rawInput observed for the two dropped cases:
{"question":"Which of these fruits do you like?","allowMultiple":true,"options":[…]}
{"question":"What is your favourite city?","allowFreeform":true}
Root cause
An ACP requestPermission reply carries exactly one optionId. There is no channel for typed text or for multiple selections, so pi-acp does not bridge those two shapes at all — it resolves the ask as cancelled.
The last row is the sharpest UX problem: pi injects ✏️ Type custom response... as a real option, so makit renders a tappable button that always cancels the question — a dead end that looks exactly like the escape hatch.
Options
- Upstream — teach
pi-acp to bridge these via ACP elicitation (unstable_createElicitation), which can carry a typed value. makit already handles elicitations (server/src/adapters/interaction.ts: single-field → input UICall), so free-text may work with no makit change. Multi-select still has no ACP representation.
- Side channel — route pi's ask off the ACP permission channel entirely (makit already owns an
askUserQuestion UICall with a multi flag).
- Stopgap — hide the
✏️ Type custom response... choice on pi/ACP sessions so users cannot tap a dead end. Cheap; removes a visible but non-functional affordance.
Reproduce
cd server
node_modules/.bin/tsx test/record-acp-session.ts --out /tmp/multi.jsonl \
--prompt "Use the ask_user tool to ask which fruits I like, let me pick MORE THAN ONE: apple, banana, cherry."
grep -c '"t":"permission"' /tmp/multi.jsonl # => 0
Requires an authenticated pi on PATH.
Affected
server/src/adapters/acp.ts — maybeSelectViaUser (only reachable when the bridge sends a permission with ≥2 options)
app/lib/ui/session/ask_card.dart — supports multi and a free-text handoff that the pi route can never exercise
Summary
Over the
pi-acpbridge, only single-choiceask_userquestions reach the phone. Multi-select and free-text questions produce no client request at all — the agent is immediately told"User cancelled the question"and continues on its own assumption. The user never learns they were asked.Pre-existing; not introduced by #104 (which fixed the single-choice and yes/no journeys). makit's own ask card already supports both shapes — the Codex adapter passes
multiSelect, and the card has a free-text handoff — so only the pi route is starved.Evidence
Recorded against real
piviaserver/test/record-acp-session.ts. Counts are client requests actually observed on the ACP connection:requestPermissioncreateElicitationallowMultiple: true("which fruits?")cancelled: true❌allowFreeform: true, no options ("favourite city?")cancelled: true❌✏️ Type custom response...cancelled: true❌Final
rawInputobserved for the two dropped cases:{"question":"Which of these fruits do you like?","allowMultiple":true,"options":[…]}{"question":"What is your favourite city?","allowFreeform":true}Root cause
An ACP
requestPermissionreply carries exactly oneoptionId. There is no channel for typed text or for multiple selections, sopi-acpdoes not bridge those two shapes at all — it resolves the ask as cancelled.The last row is the sharpest UX problem: pi injects
✏️ Type custom response...as a real option, so makit renders a tappable button that always cancels the question — a dead end that looks exactly like the escape hatch.Options
pi-acpto bridge these via ACP elicitation (unstable_createElicitation), which can carry a typed value. makit already handles elicitations (server/src/adapters/interaction.ts: single-field →inputUICall), so free-text may work with no makit change. Multi-select still has no ACP representation.askUserQuestionUICall with amultiflag).✏️ Type custom response...choice on pi/ACP sessions so users cannot tap a dead end. Cheap; removes a visible but non-functional affordance.Reproduce
Requires an authenticated
pion PATH.Affected
server/src/adapters/acp.ts—maybeSelectViaUser(only reachable when the bridge sends a permission with ≥2 options)app/lib/ui/session/ask_card.dart— supportsmultiand a free-text handoff that the pi route can never exercise