Skip to content

pi/ACP: multi-select and free-text ask_user questions never reach the phone (silently cancelled) #106

Description

@leduckhc

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

  1. 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.
  2. Side channel — route pi's ask off the ACP permission channel entirely (makit already owns an askUserQuestion UICall with a multi flag).
  3. 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.tsmaybeSelectViaUser (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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions