The single-select choice gate (#304) is inlined in planApprovalGate (run.ts), while its multi-select twin requestMultiSelect (#332) is a reusable exported primitive. showChoices() from the #326 system prompt (turn-boundary gate) and the [Research] preset (#331) both need a reusable single-select gate.
Extract requestChoices({id, title, options, recommended}) mirroring requestMultiSelect: emit the choice event, park for the pick, race-or-abort to the recommended fallback, resolve to the one picked id. Refactor planApprovalGate to call it. Preserve the #304 guarantee that a headless run (no requestChoice) is byte-identical (the plan gate keeps skipping when headless).
Foundation for #326 showChoices() and #331.
The single-select choice gate (#304) is inlined in
planApprovalGate(run.ts), while its multi-select twinrequestMultiSelect(#332) is a reusable exported primitive.showChoices()from the #326 system prompt (turn-boundary gate) and the [Research] preset (#331) both need a reusable single-select gate.Extract
requestChoices({id, title, options, recommended})mirroringrequestMultiSelect: emit thechoiceevent, park for the pick, race-or-abort to the recommended fallback, resolve to the one picked id. RefactorplanApprovalGateto call it. Preserve the #304 guarantee that a headless run (norequestChoice) is byte-identical (the plan gate keeps skipping when headless).Foundation for #326
showChoices()and #331.