Add interactive choice-picker menus for finite-choice slash commands
Slash commands with a finite set of options (/reasoning, /fast, ...) now
render a tappable card with buttons (2 per row, ✓ on the current value)
instead of a plain text status card. The mechanism is generic: any command
that calls the adapter's send_choice_picker() gets a picker automatically.
Wire protocol (docs/04, frames.schema.json):
- picker.choice (server→app): {picker_id, title, choices[]}
- picker.select (app→server): {picker_id, value}
- pickers capability flag now True in server_caps
gateway-plugin:
- protocol.py: picker.choice/picker.select frame types + picker_choice()
- dispatch.py: route picker.select → adapter.on_picker_select
- adapter.py: send_choice_picker() (fails cleanly with no live device so
hermes falls back to text), on_picker_select(), in-memory pending pickers
(gateway restart expires them; stale select is a no-op), pickers=True
app (KMP):
- Protocol.kt: PickerChoice/PickerChoicePayload + pickerSelectFrame()
- ChatStore.kt: PickerItem + onPickerChoice (idempotent) + resolvePicker
(optimistic, one-shot)
- ChatDb.kt: persist PickerItem in the messages table (polymorphic decode)
- IrisController.kt: picker.choice routing + selectPicker() action
- ChatScreen.kt: PickerCard composable (locks after selection)
Tests:
- python: 3 picker tests (roundtrip, no-device fallback, stale-select noop)
- kotlin: ChatStorePickerTest (add/idempotent/resolve/one-shot/noop/serialize)
- fixture fix: clear leaked IRIS_HTTP_PORT/IRIS_WS_HOST env so the adapter
binds the ephemeral port (a prior test's interactive_setup() polluted the
process env, colliding with a live gateway on 8791)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
34a64d6e53
commit
82c5a20848
12 files changed
+565
-8
No files matched your search
@@ -161,6 +161,16 @@ In-app banner (foreground) and/or push mirror (background).
|
||||
|
||||
Interactive prompts. App renders a native picker; answers via `picker.select`.
|
||||
|
||||
`picker.choice` is implemented (the generic finite-choice menu used by
|
||||
`/reasoning`, `/fast`, and any future finite-choice slash command — hermes
|
||||
calls the adapter's `send_choice_picker` when the platform supports it).
|
||||
The server runs the command's selection callback on `picker.select` and
|
||||
delivers its reply as a normal `message` in the picker's chat. The frame is
|
||||
outboxed (a reconnecting device re-renders a still-pending picker); pending
|
||||
state is in-memory only, so a gateway restart expires it (a stale
|
||||
`picker.select` is a no-op). With no live device the adapter reports failure
|
||||
and hermes falls back to the text status card.
|
||||
|
||||
```json
|
||||
{"type":"picker.model","chat_id":"…","payload":{
|
||||
"picker_id":"pm_1","current_model":"qwen3-27b","current_provider":"local",
|
||||
|
||||
Reference in new issue
Block a user