Add interactive choice-picker menus for finite-choice slash commands
CI / Gateway plugin tests (push) Successful in 4m48s
CI / Kotlin tests (android host + desktop) (push) Successful in 7m3s

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:
ARIAandClaude Opus 4.8 committed 2026-08-22 23:33:53 +02:00
1 parent 34a64d6e53
commit 82c5a20848
12 files changed
+565 -8

No files matched your search

+10
View File
@@ -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",