# NousResearch/hermes-agent · Inline pickers for /reasoning and /fast commands

## Open now

- 1 issue, 1 PR open on GitHub.
- #30582: Feature: Telegram inline keyboard buttons for slash commands with discrete options
- PR #64026: feat(telegram): add reasoning and fast pickers

These members relate to adding inline keyboard/picker UI elements for the /reasoning and /fast slash commands across chat platforms, particularly Telegram, bringing them to parity with the existing /model picker.

## TL;DR

- 4 member(s): 1 open issue(s), 1 closed issue(s), 1 open PR(s), 1 merged PR(s), 0 PR(s) closed without merging.
- A fix was merged to main in PR #65799. Verify it addresses your case.
- **Recommended:** read PR #65799 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-65799`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.39`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Open related issues

- #30582: Feature: Telegram inline keyboard buttons for slash commands with discrete options

## Closed / duplicate issues

- #61110: Feature: Telegram inline pickers for /reasoning and /fast (parity with /model)

## Prior & competing PRs

- **#65799: feat(gateway): inline choice pickers for /reasoning and /fast across Telegram, Discord, Matrix**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `gateway/platforms/ADDING_A_PLATFORM.md`, `gateway/slash_commands.py`, `locales/af.yaml`, `locales/de.yaml`, `locales/en.yaml`, `locales/es.yaml`, `locales/fr.yaml`, `locales/ga.yaml`, `locales/hu.yaml`, `locales/it.yaml`, `locales/ja.yaml`, `locales/ko.yaml`, `locales/pt.yaml`, `locales/ru.yaml`, `locales/tr.yaml`, `locales/uk.yaml`, `locales/zh-hant.yaml`, `locales/zh.yaml`, `plugins/platforms/discord/adapter.py`, `plugins/platforms/matrix/adapter.py` (+2 more)
  - fix hunks:
  - `gateway/platforms/ADDING_A_PLATFORM.md`

    ```diff
    @@ -120,6 +120,7 @@ If your platform supports interactive button/menu messages, implement these for
     | `send_exec_approval(chat_id, command, session_key, description, ...)` | Render dangerous-command approval as Approve/Deny buttons. Inbound dispatch routes to `tools.approval.resolve_gateway_approval`. |
     | `send_slash_confirm(chat_id, title, message, session_key, confirm_id, ...)` | Render slash-command confirmations (e.g. `/reload-mcp`) as Once/Always/Cancel buttons. Inbound dispatch routes to `tools.slash_confirm.resolve`. |
     | `send_model_picker(...)` | Interactive `/model` picker. Used by Telegram and Discord. |
    +| `send_choice_picker(...)` | Flat single-level picker for finite-choice commands (`/reasoning`, `/fast`). Implemented by Telegram (inline keyboard), Discord (select menu), and Matrix (reactions). Platforms without it fall back to the text status card automatically. |
     
     See `gateway/platforms/telegram.py`, `discord.py`, and `whatsapp_cloud.py` for reference implementations. The button-callback id convention (`cl:<id>:<idx>`, `appr:<id>:<choice>`, `sc:<choice>:<id>`) is shared across adapters — match it so the gateway-side resolvers work without modification.
     
    ```
  - `gateway/slash_commands.py`

    ```diff
    @@ -2704,7 +2704,154 @@ async def _handle_background_command(self, event: MessageEvent) -> str:
             preview = prompt[:60] + ("..." if len(prompt) > 60 else "")
             return t("gateway.background.started", preview=preview, task_id=task_id)
     
    -    async def _handle_reasoning_command(self, event: MessageEvent) -> str:
    +    def _save_gateway_config_key(self, key_path: str, value) -> bool:
    +        """Save a dot-separated key to config.yaml (shared by /reasoning, /fast
    +        and their interactive pickers)."""
    +        import yaml
    +        from gateway.run import _hermes_home
    +        config_path = _hermes_home / "config.yaml"
    +        try:
    +            user_config = {}
    +            if config_path.exists():
    +                with open(config_path, encoding="utf-8") as f:
    +                    user_config = yaml.safe_load(f) or {}
    +            keys = key_path.split(".")
    +            current = user_config
    +            for k in keys[:-1]:
    +                if k not in current or not isinstance(current[k], dict):
    +                    current[k] = {}
    +                current = current[k]
    +            current[keys[-1]] = value
    +            atomic_config_write(config_path, user_config)
    +            return True
    +        except Exception as e:
    +            logger.error("Failed to save config key %s: %s", key_path, e)
    +            return False
    +
    +    def _apply_reasoning_selection(
    +        self,
    +        session_key: str,
    +        platform_key: str,
    +        value: str,
    +        persist_global: bool = False,
    +    ) -> str:
    +        """Apply a /reasoning argument (typed or picked) and return the reply.
    +
    +        Single application path shared by the typed `/reasoning <arg>` branch
    +        and the interactive choice picker, so both surfaces stay in lockstep
    +        with the canonical parser.
    +        """
    +        from hermes_constants import parse_reasoning_effort
    +
    +        value = (value or "").strip().lower()
    +
    +        # Display toggle (per-platform)
    +        if value in {"show", "on"}:
    +            self._show_reasoning = True
    +            self._save_gateway_config_key(
    +                f"display.platforms.{platform_key}.show_reasoning", True
    +            )
    +            return t("gateway.reasoning.display_set_on", platform=platform_key)
    +        if value in {"hide", "off"}:
    +            self._show_reasoning = False
    +            self._save_gateway_config_key(
    +                f"display.platforms.{platform_key}.show_reasoning", False
    +            )
    +            return t("gateway.reasoning.display_set_off", platform=platform_key)
    +
    +        if value == "reset":
    +            if persist_global:
    +                return t("gateway.reasoning.reset_global_unsupported")
    +            self._set_session_reasoning_override(session_key, None)
    +            self._reasoning_config = self._load_reasoning_config()
    +            self._evict_cached_agent(session_key)
    +            return t("gateway.reasoning.reset_done")
    +
    +        parsed = parse_reasoning_effort(value)
    +        if parsed is None:
    +            return t("gateway.reasoning.unknown_arg", arg=value)
    +
    +        self._reasoning_config = parsed
    +        if persist_global:
    +            if self._save_gateway_config_key("agent.reasoning_effort", value):
    +                self._set_session_reasoning_override(session_key, None)
    +                self._evict_cached_agent(session_key)
    +                return t("gateway.reasoning.set_global", effort=value)
    +            self._set_session_reasoning_override(session_key, parsed)
    +            self._evict_cached_agent(session_key)
    +            return t("gateway.reasoning.set_global_save_failed", effort=value)
    +
    +        self._set_session_reasoning_override(session_key, parsed)
    +        self._evict_cached_agent(session_key)
    +        return t("gateway.reasoning.set_session", effort=value)
    +
    +    def _reasoning_picker_choices(self, current_effort: str) -> list:
    +        """Build the choice list for the interactive /reasoning picker."""
    +        from hermes_constants import VALID_REASONING_EFFORTS
    +
    +        choices = [
    +            {
    +                "value": "none",
    +                "label": t("gateway.reasoning.choice_none"),
    +                "is_current": current_effort == "none",
    +            }
    +        ]
    +        for level in VALID_REASONING_EFFORTS:
    +            choices.append(
    +                {
    +                    "value": level,
    +                    "label": level,
    +                    "is_current": level == current_effort,
    +                }
    +            )
    +        choices.extend(
    +            [
    +                {"value": "reset", "label": t("gateway.reasoning.choice_reset"), "is_current": False},
    +                {"value": "show", "label": t("gateway.reasoning.choice_show"), "is_current": False},
    +                {"value": "hide", "label": t("gateway.reasoning.choice_hide"), "is_current": False},
    +            ]
    +        )
    +        return choices
    +
    +    async def _try_send_choice_picker(
    +        self,
    +        event: MessageEvent,
    +        session_key: str,
    +        title: str,
    +        choices: list,
    +        on_choice_selected,
    +    ) -> bool:
    +        """Send an interactive choice picker when the platform supports it.
    +
    +        Mirrors the `/model` picker gate: the capability is detected on the
    +        adapter *type* (``send_choice_picker``), and a failed send falls back
    +        to the text path (returns False) instead of erroring the command.
    +        """
    +        adapter = getattr(self, "_adapter_for_source")(event.source)
    +        has_picker = (
    +            adapter is not None
    +            and getattr(type(adapter), "send_choice_picker", None) is not None
    +        )
    +        if not has_picker:
    +            return False
    +        try:
    +            metadata = self._thread_metadata_for_source(
    +                event.source, self._reply_anchor_for_event(event)
    +            )
    +            result = await adapter.send_choice_picker(
    +                chat_id=event.source.chat_id,
    +                title=title,
    +                choices=choices,
    +                session_key=session_key,
    +                on_choice_selected=on_choice_selected,
    +                metadata=metadata,
    +            )
    +            return bool(getattr(result, "success", False))
    +        except Exception as e:
    +            logger.warning("send_choice_picker failed, falling back to text: %s", e)
    +            return False
    +
    +    async def _handle_reasoning_command(self, event: MessageEvent) -> Optional[str]:
             """Handle /reasoning command — manage reasoning effort and display toggle.
     
             Usage:
    ```

    ```diff
    @@ -2715,13 +2862,10 @@ async def _handle_reasoning_command(self, event: MessageEvent) -> str:
                 /reasoning show|on               Show model reasoning in responses
                 /reasoning hide|off              Hide model reasoning from responses
             """
    -        from gateway.run import _hermes_home, _platform_config_key
    -        from hermes_constants import parse_reasoning_effort
    -        import yaml
    +        from gateway.run import _platform_config_key
     
             raw_args = event.get_command_args().strip()
             args, persist_global = self._parse_reasoning_command_args(raw_args)
    -        config_path = _hermes_home / "config.yaml"
             # Normalize the source (Telegram DM topic recovery) before deriving
             # the override key so storage matches the key the next message turn
             # reads — same fix as /model (#30479).
    ```

    ```diff
    @@ -2739,35 +2883,18 @@ async def _handle_reasoning_command(self, event: MessageEvent) -> str:
                 model=_session_model,
             )
     
    -        def _save_config_key(key_path: str, value):
    -            """Save a dot-separated key to config.yaml."""
    -            try:
    -                user_config = {}
    -                if config_path.exists():
    -                    with open(config_path, encoding="utf-8") as f:
    -                        user_config = yaml.safe_load(f) or {}
    -                keys = key_path.split(".")
    -                current = user_config
    -                for k in keys[:-1]:
    -                    if k not in current or not isinstance(current[k], dict):
    -                        current[k] = {}
    -                    current = current[k]
    -                current[keys[-1]] = value
    -                atomic_config_write(config_path, user_config)
    -                return True
    -            except Exception as e:
    -                logger.error("Failed to save config key %s: %s", key_path, e)
    -                return False
    -
             if not raw_args:
                 # Show current state
                 rc = self._reasoning_config
                 if rc is None:
                     level = t("gateway.reasoning.level_default")
    +                current_effort = "medium"
                 elif rc.get("enabled") is False:
                     level = t("gateway.reasoning.level_disabled")
    +                current_effort = "none"
                 else:
                     level = rc.get("effort", "medium")
    +                current_effort = level
                 display_state = (
                     t("gateway.reasoning.display_on")
                     if self._show_reasoning
    ```

    ```diff
    @@ -2779,6 +2906,31 @@ def _save_config_key(key_path: str, value):
                     if has_session_override
                     else t("gateway.reasoning.scope_global")
                 )
    +
    +            # Interactive picker on platforms that support it (parity with the
    +            # /model picker). Falls through to the text status card otherwise.
    +            _picker_platform_key = _platform_config_key(event.source.platform)
    +
    +            async def _on_reasoning_choice(_chat_id: str, value: str) -> str:
    +                return self._apply_reasoning_selection(
    +                    session_key, _picker_platform_key, value
    +                )
    +
    +            picker_sent = await self._try_send_choice_picker(
    +                event,
    +                session_key,
    +                title=t(
    +                    "gateway.reasoning.picker_title",
    +                    level=level,
    +                    scope=scope,
    +                    display=display_state,
    +                ),
    +                choices=self._reasoning_picker_choices(current_effort),
    +                on_choice_selected=_on_reasoning_choice,
    +            )
    +            if picker_sent:
    +                return None  # Picker sent — adapter handles the response
    +
                 return t(
                     "gateway.reasoning.status",
                     level=level,
    ```

    ```diff
    @@ -2786,47 +2938,11 @@ def _save_config_key(key_path: str, value):
                     display=display_state,
                 )
     
    -        # Display toggle (per-platform)
    +        # Typed argument path — same applier the picker uses.
             platform_key = _platform_config_key(event.source.platform)
    -        if args in {"show", "on"}:
    -            self._show_reasoning = True
    -            _save_config_key(f"display.platforms.{platform_key}.show_reasoning", True)
    -            return t("gateway.reasoning.display_set_on", platform=platform_key)
    -
    -        if args in {"hide", "off"}:
    -            self._show_reasoning = False
    -            _save_config_key(f"display.platforms.{platform_key}.show_reasoning", False)
    -            return t("gateway.reasoning.display_set_off", platform=platform_key)
    -
    -        # Effort level change
    -        effort = args.strip()
    -        if effort == "reset":
    -            if persist_global:
    -                return t("gateway.reasoning.reset_global_unsupported")
    -            self._set_session_reasoning_override(session_key, None)
    -            self._reasoning_config = self._load_reasoning_config()
    -            self._evict_cached_agent(session_key)
    -            return t("gateway.reasoning.reset_done")
    -        parsed = parse_reasoning_effort(effort)
    -        if parsed is None:
    -            return t(
    -                "gateway.reasoning.unknown_arg",
    -                arg=effort or raw_args.lower(),
    -            )
    -
    -        self._reasoning_config = parsed
    -        if persist_global:
    -            if _save_config_key("agent.reasoning_effort", effort):
    -                self._set_session_reasoning_override(session_key, None)
    -                self._evict_cached_agent(session_key)
    -                return t("gateway.reasoning.set_global", effort=effort)
    -            self._set_session_reasoning_override(session_key, parsed)
    -            self._evict_cached_agent(session_key)
    -            return t("gateway.reasoning.set_global_save_failed", effort=effort)
    -
    -        self._set_session_reasoning_override(session_key, parsed)
    -        self._evict_cached_agent(session_key)
    -        return t("gateway.reasoning.set_session", effort=effort)
    +        return self._apply_reasoning_selection(
    +            session_key, platform_key, args, persist_global=persist_global
    +        )
     
         async def _handle_memory_command(self, event: MessageEvent) -> str:
             """Handle /memory — review pending memory writes + toggle the approval gate.
    ```

    ```diff
    @@ -2932,14 +3048,12 @@ def _set_approval(enabled: bool):
                          f"~/.hermes/pending/skills/{pending_id}.json)")
             return out
     
    -    async def _handle_fast_command(self, event: MessageEvent) -> str:
    +    async def _handle_fast_command(self, event: MessageEvent) -> Optional[str]:
             """Handle /fast — mirror the CLI Priority Processing toggle in gateway chats."""
    -        from gateway.run import _hermes_home, _load_gateway_config, _resolve_gateway_model
    -        import yaml
    +        from gateway.run import _load_gateway_config, _resolve_gateway_model
             from hermes_cli.models import model_supports_fast_mode
     
             args = event.get_command_args().strip().lower()
    -        config_path = _hermes_home / "config.yaml"
             self._service_tier = self._load_service_tier()
     
             user_config = _load_gateway_config()
    ```

    ```diff
    @@ -2947,44 +3061,56 @@ async def _handle_fast_command(self, event: MessageEvent) -> str:
             if not model_supports_fast_mode(model):
                 return t("gateway.fast.not_supported")
     
    -        def _save_config_key(key_path: str, value):
    -            """Save a dot-separated key to config.yaml."""
    -            try:
    -                user_config = {}
    -                if config_path.exists():
    -                    with open(config_path, encoding="utf-8") as f:
    -                        user_config = yaml.safe_load(f) or {}
    -                keys = key_path.split(".")
    -                current = user_config
    -                for k in keys[:-1]:
    -                    if k not in current or not isinstance(current[k], dict):
    -                        current[k] = {}
    -                    current = current[k]
    -                current[keys[-1]] = value
    -                atomic_config_write(config_path, user_config)
    -                return True
    -            except Exception as e:
    -                logger.error("Failed to save config key %s: %s", key_path, e)
    -                return False
    +        def _apply_fast_selection(value: str) -> str:
    +            """Apply a /fast argument (typed or picked) and return the reply."""
    +            if value in {"fast", "on"}:
    +                self._service_tier = "priority"
    +                saved_value = "fast"
    +                label = t("gateway.fast.label_fast")
    +            elif value in {"normal", "off"}:
    +                self._service_tier = None
    +                saved_value = "normal"
    +                label = t("gateway.fast.label_normal")
    +            else:
    +                return t("gateway.fast.unknown_arg", arg=value)
    +            if self._save_gateway_config_key("agent.service_tier", saved_value):
    +                return t("gateway.fast.saved", label=label)
    +            return t("gateway.fast.session_only", label=label)
     
             if not args or args == "status":
    -            status = t("gateway.fast.status_fast") if self._service_tier == "priority" else t("gateway.fast.status_normal")
    -            return t("gateway.fast.status", mode=status)
    +            is_fast = self._service_tier == "priority"
    +            status = t("gateway.fast.status_fast") if is_fast else t("gateway.fast.status_normal")
     
    -        if args in {"fast", "on"}:
    -            self._service_tier = "priority"
    -            saved_value = "fast"
    -            label = t("gateway.fast.label_fast")
    -        elif args in {"normal", "off"}:
    -            self._service_tier = None
    -            saved_value = "normal"
    -            label = t("gateway.fast.label_normal")
    -        else:
    -            return t("gateway.fast.unknown_arg", arg=args)
    +            # Interactive picker on platforms that support it.
    +            session_key = self._session_key_for_source(event.source)
    +
    +            async def _on_fast_choice(_chat_id: str, value: str) -> str:
    +                return _apply_fast_selection(value)
    +
    +            picker_sent = await self._try_send_choice_picker(
    +                event,
    +                session_key,
    +                title=t("gateway.fast.picker_title", mode=status),
    +                choices=[
    +                    {
    +                        "value": "fast",
    +                        "label": t("gateway.fast.choice_fast"),
    +                        "is_current": is_fast,
    +                    },
    +                    {
    +                        "value": "normal",
    +                        "label": t("gateway.fast.choice_normal"),
    +                        "is_current": not is_fast,
    +                    },
    +                ],
    +                on_choice_selected=_on_fast_choice,
    +            )
    +            if picker_sent:
    +                return None  # Picker sent — adapter handles the response
    +
    +            return t("gateway.fast.status", mode=status)
     
    -        if _save_config_key("agent.service_tier", saved_value):
    -            return t("gateway.fast.saved", label=label)
    -        return t("gateway.fast.session_only", label=label)
    +        return _apply_fast_selection(args)
     
         async def _handle_yolo_command(self, event: MessageEvent) -> Union[str, EphemeralReply]:
             """Handle /yolo — toggle dangerous command approval bypass for this session only."""
    ```
  - `locales/af.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nHuidige modus: `{mode}`\\n\\nKies \\'n opsie:"
    +    choice_fast:           "fast — Priority Processing aan"
    +    choice_normal:         "normal — standaardverwerking"
     
       footer:
         status:                "📎 Looptyd-voetstuk: **{state}**\nVelde: `{fields}`\nPlatform: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` word nie ondersteun nie. Gebruik `/reasoning <level> --global` om die globale verstek te verander."
         reset_done:                "🧠 ✓ Sessie-redenering-oorskryf verwyder; val terug op globale konfigurasie."
         unknown_arg:               "⚠️ Onbekende argument: `{arg}`\n\n**Geldige vlakke:** none, minimal, low, medium, high, xhigh, max, ultra\n**Vertoon:** show, hide\n**Permanent:** voeg `--global` by om verby hierdie sessie te stoor"
    +    picker_title:              "🧠 **Redenering-instellings**\\n\\n**Inspanning:** `{level}`\\n**Bereik:** {scope}\\n**Vertoon:** {display}\\n\\nKies \\'n opsie:"
    +    choice_none:               "none — deaktiveer redenering"
    +    choice_reset:              "reset — vee sessie-oorskrywing uit"
    +    choice_show:               "wys redenering in antwoorde"
    +    choice_hide:               "versteek redenering in antwoorde"
         set_global:                "🧠 ✓ Redenering-inspanning gestel op `{effort}` (gestoor in konfigurasie)\n_(neem effek by die volgende boodskap)_"
         set_global_save_failed:    "🧠 ✓ Redenering-inspanning gestel op `{effort}` (slegs sessie — konfigurasie-stoor het misluk)\n_(neem effek by die volgende boodskap)_"
         set_session:               "🧠 ✓ Redenering-inspanning gestel op `{effort}` (slegs sessie — voeg `--global` by om permanent te stoor)\n_(neem effek by die volgende boodskap)_"
    ```
  - `locales/de.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nAktueller Modus: `{mode}`\\n\\nOption wählen:"
    +    choice_fast:           "fast — Priority Processing an"
    +    choice_normal:         "normal — Standardverarbeitung"
     
       footer:
         status:                "📎 Laufzeit-Fußzeile: **{state}**\nFelder: `{fields}`\nPlattform: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` wird nicht unterstützt. Verwenden Sie `/reasoning <level> --global`, um den globalen Standard zu ändern."
         reset_done:                "🧠 ✓ Sitzungs-Reasoning-Override gelöscht; Rückfall auf globale Konfiguration."
         unknown_arg:               "⚠️ Unbekanntes Argument: `{arg}`\n\n**Gültige Stärken:** none, minimal, low, medium, high, xhigh, max, ultra\n**Anzeige:** show, hide\n**Speichern:** `--global` hinzufügen, um über die Sitzung hinaus zu speichern"
    +    picker_title:              "🧠 **Reasoning-Einstellungen**\\n\\n**Stärke:** `{level}`\\n**Geltungsbereich:** {scope}\\n**Anzeige:** {display}\\n\\nOption wählen:"
    +    choice_none:               "none — Reasoning deaktivieren"
    +    choice_reset:              "reset — Sitzungs-Override löschen"
    +    choice_show:               "Reasoning in Antworten anzeigen"
    +    choice_hide:               "Reasoning in Antworten ausblenden"
         set_global:                "🧠 ✓ Reasoning-Stärke auf `{effort}` gesetzt (in Konfiguration gespeichert)\n_(wird mit der nächsten Nachricht wirksam)_"
         set_global_save_failed:    "🧠 ✓ Reasoning-Stärke auf `{effort}` gesetzt (nur Sitzung — Konfiguration konnte nicht gespeichert werden)\n_(wird mit der nächsten Nachricht wirksam)_"
         set_session:               "🧠 ✓ Reasoning-Stärke auf `{effort}` gesetzt (nur Sitzung — `--global` hinzufügen, um zu speichern)\n_(wird mit der nächsten Nachricht wirksam)_"
    ```
  - `locales/en.yaml`

    ```diff
    @@ -139,6 +139,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\n\nCurrent mode: `{mode}`\n\nPick an option:"
    +    choice_fast:           "fast — Priority Processing on"
    +    choice_normal:         "normal — standard processing"
     
       footer:
         status:                "📎 Runtime footer: **{state}**\nFields: `{fields}`\nPlatform: `{platform}`"
    ```

    ```diff
    @@ -209,6 +212,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` is not supported. Use `/reasoning <level> --global` to change the global default."
         reset_done:                "🧠 ✓ Session reasoning override cleared; falling back to global config."
         unknown_arg:               "⚠️ Unknown argument: `{arg}`\n\n**Valid levels:** none, minimal, low, medium, high, xhigh, max, ultra\n**Display:** show, hide\n**Persist:** add `--global` to save beyond this session"
    +    picker_title:              "🧠 **Reasoning Settings**\n\n**Effort:** `{level}`\n**Scope:** {scope}\n**Display:** {display}\n\nPick an option:"
    +    choice_none:               "none — disable reasoning"
    +    choice_reset:              "reset — clear session override"
    +    choice_show:               "show reasoning in replies"
    +    choice_hide:               "hide reasoning from replies"
         set_global:                "🧠 ✓ Reasoning effort set to `{effort}` (saved to config)\n_(takes effect on next message)_"
         set_global_save_failed:    "🧠 ✓ Reasoning effort set to `{effort}` (session only — config save failed)\n_(takes effect on next message)_"
         set_session:               "🧠 ✓ Reasoning effort set to `{effort}` (session only — add `--global` to persist)\n_(takes effect on next message)_"
    ```
  - `locales/es.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nModo actual: `{mode}`\\n\\nElige una opción:"
    +    choice_fast:           "fast — Priority Processing activado"
    +    choice_normal:         "normal — procesamiento estándar"
     
       footer:
         status:                "📎 Pie de ejecución: **{state}**\nCampos: `{fields}`\nPlataforma: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` no es compatible. Usa `/reasoning <level> --global` para cambiar el valor global por defecto."
         reset_done:                "🧠 ✓ Anulación de razonamiento de la sesión borrada; volviendo a la configuración global."
         unknown_arg:               "⚠️ Argumento desconocido: `{arg}`\n\n**Niveles válidos:** none, minimal, low, medium, high, xhigh, max, ultra\n**Visualización:** show, hide\n**Persistir:** añade `--global` para guardar más allá de esta sesión"
    +    picker_title:              "🧠 **Ajustes de razonamiento**\\n\\n**Esfuerzo:** `{level}`\\n**Alcance:** {scope}\\n**Visualización:** {display}\\n\\nElige una opción:"
    +    choice_none:               "none — desactivar razonamiento"
    +    choice_reset:              "reset — borrar el ajuste de sesión"
    +    choice_show:               "mostrar razonamiento en respuestas"
    +    choice_hide:               "ocultar razonamiento en respuestas"
         set_global:                "🧠 ✓ Esfuerzo de razonamiento ajustado a `{effort}` (guardado en la configuración)\n_(se aplica en el próximo mensaje)_"
         set_global_save_failed:    "🧠 ✓ Esfuerzo de razonamiento ajustado a `{effort}` (solo en la sesión — error al guardar la configuración)\n_(se aplica en el próximo mensaje)_"
         set_session:               "🧠 ✓ Esfuerzo de razonamiento ajustado a `{effort}` (solo en la sesión — añade `--global` para persistir)\n_(se aplica en el próximo mensaje)_"
    ```
  - `locales/fr.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nMode actuel : `{mode}`\\n\\nChoisissez une option :"
    +    choice_fast:           "fast — Priority Processing activé"
    +    choice_normal:         "normal — traitement standard"
     
       footer:
         status:                "📎 Pied de page d'exécution : **{state}**\nChamps : `{fields}`\nPlateforme : `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` n'est pas pris en charge. Utilisez `/reasoning <level> --global` pour modifier la valeur globale par défaut."
         reset_done:                "🧠 ✓ Remplacement de raisonnement de la session effacé ; retour à la configuration globale."
         unknown_arg:               "⚠️ Argument inconnu : `{arg}`\n\n**Niveaux valides :** none, minimal, low, medium, high, xhigh, max, ultra\n**Affichage :** show, hide\n**Persister :** ajoutez `--global` pour enregistrer au-delà de cette session"
    +    picker_title:              "🧠 **Paramètres de raisonnement**\\n\\n**Effort :** `{level}`\\n**Portée :** {scope}\\n**Affichage :** {display}\\n\\nChoisissez une option :"
    +    choice_none:               "none — désactiver le raisonnement"
    +    choice_reset:              "reset — effacer le réglage de session"
    +    choice_show:               "afficher le raisonnement dans les réponses"
    +    choice_hide:               "masquer le raisonnement dans les réponses"
         set_global:                "🧠 ✓ Effort de raisonnement défini sur `{effort}` (enregistré dans la configuration)\n_(prend effet au prochain message)_"
         set_global_save_failed:    "🧠 ✓ Effort de raisonnement défini sur `{effort}` (session uniquement — échec de l'enregistrement de la configuration)\n_(prend effet au prochain message)_"
         set_session:               "🧠 ✓ Effort de raisonnement défini sur `{effort}` (session uniquement — ajoutez `--global` pour persister)\n_(prend effet au prochain message)_"
    ```
  - `locales/ga.yaml`

    ```diff
    @@ -128,6 +128,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nMód reatha: `{mode}`\\n\\nRoghnaigh rogha:"
    +    choice_fast:           "fast — Priority Processing ar siúl"
    +    choice_normal:         "normal — gnáthphróiseáil"
     
       footer:
         status:                "📎 Buntásc rite: **{state}**\nRéimsí: `{fields}`\nArdán: `{platform}`"
    ```

    ```diff
    @@ -198,6 +201,11 @@ gateway:
         reset_global_unsupported:  "⚠️ Ní thacaítear le `/reasoning reset --global`. Úsáid `/reasoning <level> --global` chun an réamhshocrú domhanda a athrú."
         reset_done:                "🧠 ✓ Sárú réasúnaíochta seisiúin glanta; ag titim siar ar an gcumraíocht dhomhanda."
         unknown_arg:               "⚠️ Argóint anaithnid: `{arg}`\n\n**Leibhéil bhailí:** none, minimal, low, medium, high, xhigh, max, ultra\n**Taispeáint:** show, hide\n**Coinnigh:** cuir `--global` leis chun sábháil thar an seisiún seo"
    +    picker_title:              "🧠 **Socruithe Réasúnaíochta**\\n\\n**Iarracht:** `{level}`\\n**Scóip:** {scope}\\n**Taispeáint:** {display}\\n\\nRoghnaigh rogha:"
    +    choice_none:               "none — díchumasaigh réasúnaíocht"
    +    choice_reset:              "reset — glan sárú an tseisiúin"
    +    choice_show:               "taispeáin réasúnaíocht sna freagraí"
    +    choice_hide:               "folaigh réasúnaíocht ó na freagraí"
         set_global:                "🧠 ✓ Iarracht réasúnaíochta socraithe go `{effort}` (sábháilte sa chumraíocht)\n_(éifeachtach ón gcéad teachtaireacht eile)_"
         set_global_save_failed:    "🧠 ✓ Iarracht réasúnaíochta socraithe go `{effort}` (seisiún amháin — theip ar shábháil cumraíochta)\n_(éifeachtach ón gcéad teachtaireacht eile)_"
         set_session:               "🧠 ✓ Iarracht réasúnaíochta socraithe go `{effort}` (seisiún amháin — cuir `--global` leis chun é a choinneáil)\n_(éifeachtach ón gcéad teachtaireacht eile)_"
    ```
  - `locales/hu.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nJelenlegi mód: `{mode}`\\n\\nVálassz egy opciót:"
    +    choice_fast:           "fast — Priority Processing bekapcsolva"
    +    choice_normal:         "normal — normál feldolgozás"
     
       footer:
         status:                "📎 Futási idejű lábléc: **{state}**\nMezők: `{fields}`\nPlatform: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ A `/reasoning reset --global` nem támogatott. Használd a `/reasoning <level> --global` parancsot a globális alapérték módosításához."
         reset_done:                "🧠 ✓ A munkamenet gondolkodási felülbírálása törölve; visszaállás a globális konfigurációra."
         unknown_arg:               "⚠️ Ismeretlen argumentum: `{arg}`\n\n**Érvényes szintek:** none, minimal, low, medium, high, xhigh, max, ultra\n**Megjelenítés:** show, hide\n**Megőrzés:** add hozzá a `--global` opciót a munkameneten túli mentéshez"
    +    picker_title:              "🧠 **Gondolkodási beállítások**\\n\\n**Erőfeszítés:** `{level}`\\n**Hatókör:** {scope}\\n**Megjelenítés:** {display}\\n\\nVálassz egy opciót:"
    +    choice_none:               "none — gondolkodás kikapcsolása"
    +    choice_reset:              "reset — munkameneti felülbírálás törlése"
    +    choice_show:               "gondolkodás megjelenítése a válaszokban"
    +    choice_hide:               "gondolkodás elrejtése a válaszokból"
         set_global:                "🧠 ✓ Gondolkodási erőfeszítés beállítva: `{effort}` (mentve a konfigurációba)\n_(a következő üzenettől lép életbe)_"
         set_global_save_failed:    "🧠 ✓ Gondolkodási erőfeszítés beállítva: `{effort}` (csak ebben a munkamenetben — a konfiguráció mentése sikertelen)\n_(a következő üzenettől lép életbe)_"
         set_session:               "🧠 ✓ Gondolkodási erőfeszítés beállítva: `{effort}` (csak ebben a munkamenetben — add hozzá a `--global` opciót a megőrzéshez)\n_(a következő üzenettől lép életbe)_"
    ```
  - `locales/it.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nModalità attuale: `{mode}`\\n\\nScegli un\\'opzione:"
    +    choice_fast:           "fast — Priority Processing attivo"
    +    choice_normal:         "normal — elaborazione standard"
     
       footer:
         status:                "📎 Footer di runtime: **{state}**\nCampi: `{fields}`\nPiattaforma: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` non è supportato. Usa `/reasoning <level> --global` per cambiare il valore predefinito globale."
         reset_done:                "🧠 ✓ Override di reasoning della sessione cancellato; ripristino della configurazione globale."
         unknown_arg:               "⚠️ Argomento sconosciuto: `{arg}`\n\n**Livelli validi:** none, minimal, low, medium, high, xhigh, max, ultra\n**Visualizzazione:** show, hide\n**Persistenza:** aggiungi `--global` per salvare oltre questa sessione"
    +    picker_title:              "🧠 **Impostazioni di reasoning**\\n\\n**Sforzo:** `{level}`\\n**Ambito:** {scope}\\n**Visualizzazione:** {display}\\n\\nScegli un\\'opzione:"
    +    choice_none:               "none — disattiva il reasoning"
    +    choice_reset:              "reset — cancella l'override di sessione"
    +    choice_show:               "mostra il reasoning nelle risposte"
    +    choice_hide:               "nascondi il reasoning dalle risposte"
         set_global:                "🧠 ✓ Sforzo di reasoning impostato su `{effort}` (salvato nella configurazione)\n_(verrà applicato al prossimo messaggio)_"
         set_global_save_failed:    "🧠 ✓ Sforzo di reasoning impostato su `{effort}` (solo per questa sessione — salvataggio della configurazione non riuscito)\n_(verrà applicato al prossimo messaggio)_"
         set_session:               "🧠 ✓ Sforzo di reasoning impostato su `{effort}` (solo per questa sessione — aggiungi `--global` per renderlo permanente)\n_(verrà applicato al prossimo messaggio)_"
    ```
  - `locales/ja.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\n現在のモード: `{mode}`\\n\\nオプションを選択:"
    +    choice_fast:           "fast — Priority Processing オン"
    +    choice_normal:         "normal — 標準処理"
     
       footer:
         status:                "📎 ランタイムフッター: **{state}**\nフィールド: `{fields}`\nプラットフォーム: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` はサポートされていません。グローバルのデフォルトを変更するには `/reasoning <level> --global` を使用してください。"
         reset_done:                "🧠 ✓ セッションの推論オーバーライドをクリアしました。グローバル設定にフォールバックします。"
         unknown_arg:               "⚠️ 不明な引数: `{arg}`\n\n**有効なレベル:** none, minimal, low, medium, high, xhigh, max, ultra\n**表示:** show, hide\n**永続化:** セッションを越えて保存するには `--global` を追加"
    +    picker_title:              "🧠 **推論設定**\\n\\n**強度:** `{level}`\\n**スコープ:** {scope}\\n**表示:** {display}\\n\\nオプションを選択:"
    +    choice_none:               "none — 推論を無効化"
    +    choice_reset:              "reset — セッション上書きをクリア"
    +    choice_show:               "返信に推論を表示"
    +    choice_hide:               "返信の推論を非表示"
         set_global:                "🧠 ✓ 推論強度を `{effort}` に設定しました (設定に保存)\n_(次のメッセージから有効)_"
         set_global_save_failed:    "🧠 ✓ 推論強度を `{effort}` に設定しました (セッションのみ — 設定の保存に失敗)\n_(次のメッセージから有効)_"
         set_session:               "🧠 ✓ 推論強度を `{effort}` に設定しました (セッションのみ — 永続化するには `--global` を追加)\n_(次のメッセージから有効)_"
    ```
  - `locales/ko.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\n현재 모드: `{mode}`\\n\\n옵션을 선택하세요:"
    +    choice_fast:           "fast — Priority Processing 켜기"
    +    choice_normal:         "normal — 표준 처리"
     
       footer:
         status:                "📎 런타임 푸터: **{state}**\n필드: `{fields}`\n플랫폼: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global`은 지원되지 않습니다. 전역 기본값을 변경하려면 `/reasoning <level> --global`을 사용하세요."
         reset_done:                "🧠 ✓ 세션 추론 재정의가 해제되었습니다. 전역 설정으로 돌아갑니다."
         unknown_arg:               "⚠️ 알 수 없는 인수: `{arg}`\n\n**유효한 수준:** none, minimal, low, medium, high, xhigh, max, ultra\n**표시:** show, hide\n**영구화:** 이 세션을 넘어 저장하려면 `--global`을 추가하세요"
    +    picker_title:              "🧠 **추론 설정**\\n\\n**노력:** `{level}`\\n**범위:** {scope}\\n**표시:** {display}\\n\\n옵션을 선택하세요:"
    +    choice_none:               "none — 추론 비활성화"
    +    choice_reset:              "reset — 세션 재정의 초기화"
    +    choice_show:               "응답에 추론 표시"
    +    choice_hide:               "응답에서 추론 숨기기"
         set_global:                "🧠 ✓ 추론 노력이 `{effort}`(으)로 설정되었습니다 (설정에 저장됨)\n_(다음 메시지부터 적용됩니다)_"
         set_global_save_failed:    "🧠 ✓ 추론 노력이 `{effort}`(으)로 설정되었습니다 (세션 한정 — 설정 저장 실패)\n_(다음 메시지부터 적용됩니다)_"
         set_session:               "🧠 ✓ 추론 노력이 `{effort}`(으)로 설정되었습니다 (세션 한정 — 영구 저장하려면 `--global` 추가)\n_(다음 메시지부터 적용됩니다)_"
    ```
  - `locales/pt.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nModo atual: `{mode}`\\n\\nEscolha uma opção:"
    +    choice_fast:           "fast — Priority Processing ativado"
    +    choice_normal:         "normal — processamento padrão"
     
       footer:
         status:                "📎 Rodapé de execução: **{state}**\nCampos: `{fields}`\nPlataforma: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` não é suportado. Usa `/reasoning <level> --global` para alterar o predefinido global."
         reset_done:                "🧠 ✓ Substituição de raciocínio da sessão removida; a regressar à configuração global."
         unknown_arg:               "⚠️ Argumento desconhecido: `{arg}`\n\n**Níveis válidos:** none, minimal, low, medium, high, xhigh, max, ultra\n**Visualização:** show, hide\n**Persistir:** adiciona `--global` para guardar para além desta sessão"
    +    picker_title:              "🧠 **Definições de raciocínio**\\n\\n**Esforço:** `{level}`\\n**Âmbito:** {scope}\\n**Visualização:** {display}\\n\\nEscolha uma opção:"
    +    choice_none:               "none — desativar raciocínio"
    +    choice_reset:              "reset — limpar o override de sessão"
    +    choice_show:               "mostrar raciocínio nas respostas"
    +    choice_hide:               "ocultar raciocínio das respostas"
         set_global:                "🧠 ✓ Esforço de raciocínio definido como `{effort}` (guardado na configuração)\n_(produz efeito na próxima mensagem)_"
         set_global_save_failed:    "🧠 ✓ Esforço de raciocínio definido como `{effort}` (apenas sessão — falha ao guardar a configuração)\n_(produz efeito na próxima mensagem)_"
         set_session:               "🧠 ✓ Esforço de raciocínio definido como `{effort}` (apenas sessão — adiciona `--global` para persistir)\n_(produz efeito na próxima mensagem)_"
    ```
  - `locales/ru.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nТекущий режим: `{mode}`\\n\\nВыберите вариант:"
    +    choice_fast:           "fast — Priority Processing включён"
    +    choice_normal:         "normal — стандартная обработка"
     
       footer:
         status:                "📎 Нижний колонтитул среды выполнения: **{state}**\nПоля: `{fields}`\nПлатформа: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` не поддерживается. Используйте `/reasoning <level> --global`, чтобы изменить глобальное значение по умолчанию."
         reset_done:                "🧠 ✓ Переопределение рассуждений для сеанса сброшено; возврат к глобальной конфигурации."
         unknown_arg:               "⚠️ Неизвестный аргумент: `{arg}`\n\n**Допустимые уровни:** none, minimal, low, medium, high, xhigh, max, ultra\n**Отображение:** show, hide\n**Сохранение:** добавьте `--global`, чтобы сохранить за пределами этого сеанса"
    +    picker_title:              "🧠 **Настройки рассуждений**\\n\\n**Усилия:** `{level}`\\n**Область:** {scope}\\n**Отображение:** {display}\\n\\nВыберите вариант:"
    +    choice_none:               "none — отключить рассуждения"
    +    choice_reset:              "reset — сбросить переопределение сессии"
    +    choice_show:               "показывать рассуждения в ответах"
    +    choice_hide:               "скрывать рассуждения в ответах"
         set_global:                "🧠 ✓ Усилия рассуждений установлены на `{effort}` (сохранено в конфигурации)\n_(вступит в силу со следующего сообщения)_"
         set_global_save_failed:    "🧠 ✓ Усилия рассуждений установлены на `{effort}` (только этот сеанс — не удалось сохранить конфигурацию)\n_(вступит в силу со следующего сообщения)_"
         set_session:               "🧠 ✓ Усилия рассуждений установлены на `{effort}` (только этот сеанс — добавьте `--global`, чтобы сохранить)\n_(вступит в силу со следующего сообщения)_"
    ```
  - `locales/tr.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nMevcut mod: `{mode}`\\n\\nBir seçenek seçin:"
    +    choice_fast:           "fast — Priority Processing açık"
    +    choice_normal:         "normal — standart işleme"
     
       footer:
         status:                "📎 Çalışma zamanı altbilgisi: **{state}**\nAlanlar: `{fields}`\nPlatform: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` desteklenmiyor. Genel varsayılanı değiştirmek için `/reasoning <level> --global` kullanın."
         reset_done:                "🧠 ✓ Oturumun akıl yürütme geçersiz kılması temizlendi; genel yapılandırmaya geri dönülüyor."
         unknown_arg:               "⚠️ Bilinmeyen argüman: `{arg}`\n\n**Geçerli seviyeler:** none, minimal, low, medium, high, xhigh, max, ultra\n**Görüntüleme:** show, hide\n**Kalıcı:** bu oturumun ötesinde kaydetmek için `--global` ekleyin"
    +    picker_title:              "🧠 **Akıl Yürütme Ayarları**\\n\\n**Güç:** `{level}`\\n**Kapsam:** {scope}\\n**Görüntüleme:** {display}\\n\\nBir seçenek seçin:"
    +    choice_none:               "none — akıl yürütmeyi kapat"
    +    choice_reset:              "reset — oturum geçersiz kılmasını temizle"
    +    choice_show:               "yanıtlarda akıl yürütmeyi göster"
    +    choice_hide:               "yanıtlarda akıl yürütmeyi gizle"
         set_global:                "🧠 ✓ Akıl yürütme gücü `{effort}` olarak ayarlandı (yapılandırmaya kaydedildi)\n_(sonraki mesajda etkili)_"
         set_global_save_failed:    "🧠 ✓ Akıl yürütme gücü `{effort}` olarak ayarlandı (yalnızca bu oturum — yapılandırma kaydedilemedi)\n_(sonraki mesajda etkili)_"
         set_session:               "🧠 ✓ Akıl yürütme gücü `{effort}` olarak ayarlandı (yalnızca bu oturum — kalıcı yapmak için `--global` ekleyin)\n_(sonraki mesajda etkili)_"
    ```
  - `locales/uk.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\nПоточний режим: `{mode}`\\n\\nОберіть варіант:"
    +    choice_fast:           "fast — Priority Processing увімкнено"
    +    choice_normal:         "normal — стандартна обробка"
     
       footer:
         status:                "📎 Нижній колонтитул середовища: **{state}**\nПоля: `{fields}`\nПлатформа: `{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ `/reasoning reset --global` не підтримується. Використовуйте `/reasoning <level> --global`, щоб змінити глобальне значення за замовчуванням."
         reset_done:                "🧠 ✓ Перевизначення мислення для сеансу скинуто; повернення до глобальної конфігурації."
         unknown_arg:               "⚠️ Невідомий аргумент: `{arg}`\n\n**Дійсні рівні:** none, minimal, low, medium, high, xhigh, max, ultra\n**Показ:** show, hide\n**Зберегти:** додайте `--global`, щоб зберегти поза цим сеансом"
    +    picker_title:              "🧠 **Налаштування мислення**\\n\\n**Зусилля:** `{level}`\\n**Область:** {scope}\\n**Показ:** {display}\\n\\nОберіть варіант:"
    +    choice_none:               "none — вимкнути мислення"
    +    choice_reset:              "reset — скинути перевизначення сесії"
    +    choice_show:               "показувати мислення у відповідях"
    +    choice_hide:               "приховувати мислення у відповідях"
         set_global:                "🧠 ✓ Зусилля мислення встановлено на `{effort}` (збережено в конфігурації)\n_(набуде чинності з наступного повідомлення)_"
         set_global_save_failed:    "🧠 ✓ Зусилля мислення встановлено на `{effort}` (лише цей сеанс — не вдалося зберегти конфігурацію)\n_(набуде чинності з наступного повідомлення)_"
         set_session:               "🧠 ✓ Зусилля мислення встановлено на `{effort}` (лише цей сеанс — додайте `--global`, щоб зберегти)\n_(набуде чинності з наступного повідомлення)_"
    ```
  - `locales/zh-hant.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **Priority Processing**\\n\\n目前模式：`{mode}`\\n\\n請選擇："
    +    choice_fast:           "fast — 開啟 Priority Processing"
    +    choice_normal:         "normal — 標準處理"
     
       footer:
         status:                "📎 執行階段頁尾：**{state}**\n欄位：`{fields}`\n平台：`{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ 不支援 `/reasoning reset --global`。請使用 `/reasoning <level> --global` 變更全域預設值。"
         reset_done:                "🧠 ✓ 已清除本工作階段的推理覆寫；回退至全域設定。"
         unknown_arg:               "⚠️ 未知參數：`{arg}`\n\n**有效級別：** none, minimal, low, medium, high, xhigh, max, ultra\n**顯示：** show, hide\n**持久化：** 加上 `--global` 可跨工作階段儲存"
    +    picker_title:              "🧠 **推理設定**\\n\\n**強度：** `{level}`\\n**範圍：** {scope}\\n**顯示：** {display}\\n\\n請選擇："
    +    choice_none:               "none — 停用推理"
    +    choice_reset:              "reset — 清除工作階段覆寫"
    +    choice_show:               "在回覆中顯示推理"
    +    choice_hide:               "在回覆中隱藏推理"
         set_global:                "🧠 ✓ 推理強度已設定為 `{effort}`（已儲存到設定）\n_（下一則訊息生效）_"
         set_global_save_failed:    "🧠 ✓ 推理強度已設定為 `{effort}`（僅本工作階段 — 設定儲存失敗）\n_（下一則訊息生效）_"
         set_session:               "🧠 ✓ 推理強度已設定為 `{effort}`（僅本工作階段 — 加上 `--global` 可持久化）\n_（下一則訊息生效）_"
    ```
  - `locales/zh.yaml`

    ```diff
    @@ -124,6 +124,9 @@ gateway:
         label_normal:          "NORMAL"
         status_fast:           "fast"
         status_normal:         "normal"
    +    picker_title:          "⚡ **优先处理**\\n\\n当前模式：`{mode}`\\n\\n请选择："
    +    choice_fast:           "fast — 开启优先处理"
    +    choice_normal:         "normal — 标准处理"
     
       footer:
         status:                "📎 运行时页脚：**{state}**\n字段：`{fields}`\n平台：`{platform}`"
    ```

    ```diff
    @@ -194,6 +197,11 @@ gateway:
         reset_global_unsupported:  "⚠️ 不支持 `/reasoning reset --global`。请使用 `/reasoning <level> --global` 修改全局默认值。"
         reset_done:                "🧠 ✓ 已清除本会话的推理覆盖；回退到全局配置。"
         unknown_arg:               "⚠️ 未知参数：`{arg}`\n\n**有效级别：** none, minimal, low, medium, high, xhigh, max, ultra\n**显示：** show, hide\n**持久化：** 添加 `--global` 以跨会话保存"
    +    picker_title:              "🧠 **推理设置**\\n\\n**强度：** `{level}`\\n**作用域：** {scope}\\n**显示：** {display}\\n\\n请选择："
    +    choice_none:               "none — 关闭推理"
    +    choice_reset:              "reset — 清除会话覆盖"
    +    choice_show:               "在回复中显示推理"
    +    choice_hide:               "在回复中隐藏推理"
         set_global:                "🧠 ✓ 推理强度已设置为 `{effort}`（已保存到配置）\n_（下一条消息生效）_"
         set_global_save_failed:    "🧠 ✓ 推理强度已设置为 `{effort}`（仅本会话 — 配置保存失败）\n_（下一条消息生效）_"
         set_session:               "🧠 ✓ 推理强度已设置为 `{effort}`（仅本会话 — 添加 `--global` 以持久化）\n_（下一条消息生效）_"
    ```
  - `plugins/platforms/discord/adapter.py`

    ```diff
    @@ -5961,6 +5961,54 @@ async def send_model_picker(
                 logger.warning("[%s] send_model_picker failed: %s", self.name, e)
                 return SendResult(success=False, error=str(e))
     
    +    async def send_choice_picker(
    +        self,
    +        chat_id: str,
    +        title: str,
    +        choices: list,
    +        session_key: str,
    +        on_choice_selected,
    +        metadata: Optional[Dict[str, Any]] = None,
    +    ) -> SendResult:
    +        """Send a flat select-menu choice picker (one selection → one value).
    +
    +        Generic single-level companion to ``send_model_picker`` used by
    +        `/reasoning`, `/fast`, and any future finite-choice command. Each
    +        choice dict: ``{"value": str, "label": str, "is_current": bool}``.
    +        """
    +        if not self._client or not DISCORD_AVAILABLE:
    +            return SendResult(success=False, error="Not connected")
    +
    +        try:
    +            target_id = chat_id
    +            if metadata and metadata.get("thread_id"):
    +                target_id = metadata["thread_id"]
    +
    +            channel = self._client.get_channel(int(target_id))
    +            if not channel:
    +                channel = await self._client.fetch_channel(int(target_id))
    +
    +            embed = discord.Embed(
    +                title="⚙ " + (title.splitlines()[0] if title else "Choose an option"),
    +                description="\n".join(title.splitlines()[1:]) or None,
    +                color=discord.Color.blue(),
    +            )
    +
    +            view = ChoicePickerView(
    +                choices=choices,
    +                on_choice_selected=on_choice_selected,
    +                allowed_user_ids=self._allowed_user_ids,
    +                allowed_role_ids=self._allowed_role_ids,
    +            )
    +
    +            msg = await channel.send(embed=embed, view=view)
    +            view._message = msg  # store for on_timeout expiration editing
    +            return SendResult(success=True, message_id=str(msg.id))
    +
    +        except Exception as e:
    +            logger.warning("[%s] send_choice_picker failed: %s", self.name, e)
    +            return SendResult(success=False, error=str(e))
    +
         def _get_parent_channel_id(self, channel: Any) -> Optional[str]:
             """Return the parent channel ID for a Discord thread-like channel, if present."""
             parent = getattr(channel, "parent", None)
    ```

    ```diff
    @@ -6850,7 +6898,7 @@ def _define_discord_view_classes() -> None:
         lazy install sets DISCORD_AVAILABLE=True but leaves the classes
         undefined, causing NameError on the first button interaction.
         """
    -    global ExecApprovalView, SlashConfirmView, UpdatePromptView, ModelPickerView, ClarifyChoiceView
    +    global ExecApprovalView, SlashConfirmView, UpdatePromptView, ModelPickerView, ClarifyChoiceView, ChoicePickerView
     
         class ExecApprovalView(discord.ui.View):
             """
    ```

    ```diff
    @@ -7547,6 +7595,97 @@ async def on_timeout(self):
                         pass
     
     
    +    class ChoicePickerView(discord.ui.View):
    +        """Flat select-menu view for finite-choice commands (/reasoning, /fast).
    +
    +        One dropdown, one selection, done — the generic single-level companion
    +        to ``ModelPickerView``. Auth gating mirrors ``ExecApprovalView``.
    +        Times out after 2 minutes.
    +        """
    +
    +        def __init__(
    +            self,
    +            choices: list,
    +            on_choice_selected,
    +            allowed_user_ids: set,
    +            allowed_role_ids: Optional[set] = None,
    +        ):
    +            super().__init__(timeout=120)
    +            self.choices = list(choices)[:25]  # Discord select cap
    +            self.on_choice_selected = on_choice_selected
    +            self.allowed_user_ids = allowed_user_ids
    +            self.allowed_role_ids = allowed_role_ids or set()
    +            self.resolved = False
    +            self._message = None
    +
    +            options = []
    +            for choice in self.choices:
    +                label = str(choice.get("label") or choice.get("value") or "")
    +                options.append(
    +                    discord.SelectOption(
    +                        label=_truncate_discord_component_text(
    +                            label, _DISCORD_SELECT_FIELD_LIMIT
    +                        ),
    +                        value=str(choice.get("value") or ""),
    +                        description="current" if choice.get("is_current") else None,
    +                    )
    +                )
    +            select = discord.ui.Select(
    +                placeholder="Choose an option...",
    +                options=options,
    +            )
    +            select.callback = self._on_select
    +            self.add_item(select)
    +
    +        def _check_auth(self, interaction: discord.Interaction) -> bool:
    +            return _component_check_auth(
    +                interaction, self.allowed_user_ids, self.allowed_role_ids,
    +            )
    +
    +        async def _on_select(self, interaction: discord.Interaction):
    +            if not self._check_auth(interaction):
    +                await interaction.response.send_message(
    +                    "⛔ You are not authorized to change this setting.",
    +                    ephemeral=True,
    +                )
    +                return
    +            if self.resolved:
    +                await interaction.response.defer()
    +                return
    +            self.resolved = True
    +
    +            value = interaction.data.get("values", [""])[0]
    +            try:
    +                result_text = await self.on_choice_selected(
    +                    str(interaction.channel_id), value
    +                )
    +            except Exception as exc:
    +                logger.error("Choice picker selection failed: %s", exc)
    +                result_text = f"Error applying selection: {exc}"
    +
    +            embed = discord.Embed(
    +                description=result_text,
    +                color=discord.Color.green(),
    +            )
    +            self.clear_items()
    +            self.stop()
    +            await interaction.response.edit_message(embed=embed, view=self)
    +
    +        async def on_timeout(self):
    +            if self.resolved:
    +                return
    +            msg = self._message
    +            if msg is not None:
    +                try:
    +                    embed = discord.Embed(
    +                        description="⏱ Selection expired — no change made.",
    +                        color=discord.Color.greyple(),
    +                    )
    +                    self.clear_items()
    +                    await msg.edit(embed=embed, view=self)
    +                except Exception:
    +                    pass
    +
         class ClarifyChoiceView(discord.ui.View):
             """Interactive button view for the clarify tool's multiple-choice prompts.
     
    ```
  - `plugins/platforms/matrix/adapter.py`

    ```diff
    @@ -337,6 +337,21 @@ class _MatrixModelPickerPrompt:
         bot_reaction_events: dict[str, str] = field(default_factory=dict)
     
     
    +@dataclass
    +class _MatrixChoicePickerPrompt:
    +    """Tracks a pending Matrix reaction-based choice picker (/reasoning, /fast)."""
    +
    +    chat_id: str
    +    message_id: str
    +    session_key: str
    +    choices: dict[str, str]  # emoji -> value
    +    on_choice_selected: Any
    +    requester_user_id: str | None = None
    +    expires_at: float | None = None
    +    resolved: bool = False
    +    bot_reaction_events: dict[str, str] = field(default_factory=dict)
    +
    +
     # Matrix message size limit (4000 chars practical, spec has no hard limit
     # but clients render poorly above this).
     MAX_MESSAGE_LENGTH = 4000
    ```

    ```diff
    @@ -386,6 +401,14 @@ class _MatrixModelPickerPrompt:
         "\U0001f51f",
     )
     
    +# Choice pickers (/reasoning, /fast) can need more than 10 slots
    +# (8 effort levels + none + reset/show/hide = 12), so extend the keycap
    +# set with lettered squares.
    +_MATRIX_CHOICE_PICKER_REACTIONS = _MATRIX_MODEL_PICKER_REACTIONS + (
    +    "\U0001f170\ufe0f",  # 🅰️
    +    "\U0001f171\ufe0f",  # 🅱️
    +)
    +
     _MATRIX_CAPABILITIES: Dict[str, str] = {
         "text": "yes",
         "threads": "yes",
    ```

    ```diff
    @@ -954,6 +977,7 @@ def __init__(self, config: PlatformConfig):
             except ValueError:
                 self._approval_timeout_seconds = 300
             self._model_picker_prompts_by_event: Dict[str, _MatrixModelPickerPrompt] = {}
    +        self._choice_picker_prompts_by_event: Dict[str, _MatrixChoicePickerPrompt] = {}
             allowed_users_raw = os.getenv("MATRIX_ALLOWED_USERS", "")
             self._allowed_user_ids: Set[str] = {
                 u.strip() for u in allowed_users_raw.split(",") if u.strip()
    ```

    ```diff
    @@ -2136,6 +2160,66 @@ async def send_model_picker(
     
             return result
     
    +    async def send_choice_picker(
    +        self,
    +        chat_id: str,
    +        title: str,
    +        choices: list,
    +        session_key: str,
    +        on_choice_selected,
    +        metadata: Optional[Dict[str, Any]] = None,
    +    ) -> SendResult:
    +        """Send a Matrix reaction-based choice picker (/reasoning, /fast).
    +
    +        Generic single-level companion to ``send_model_picker``. Each choice
    +        dict: ``{"value": str, "label": str, "is_current": bool}``.
    +        """
    +        if not self._client:
    +            return SendResult(success=False, error="Not connected")
    +
    +        emoji_choices: dict[str, str] = {}
    +        lines = [title, ""]
    +        for i, choice in enumerate(choices):
    +            if i >= len(_MATRIX_CHOICE_PICKER_REACTIONS):
    +                break
    +            emoji = _MATRIX_CHOICE_PICKER_REACTIONS[i]
    +            value = str(choice.get("value") or "")
    +            label = str(choice.get("label") or value)
    +            if choice.get("is_current"):
    +                label = f"{label} ← current"
    +            emoji_choices[emoji] = value
    +            lines.append(f"{emoji} {label}")
    +
    +        if not emoji_choices:
    +            return SendResult(success=False, error="No choices")
    +
    +        lines.append("")
    +        lines.append("React to choose.")
    +        result = await self.send(chat_id, "\n".join(lines), metadata=metadata)
    +        if not result.success or not result.message_id:
    +            return result
    +
    +        prompt = _MatrixChoicePickerPrompt(
    +            chat_id=chat_id,
    +            message_id=result.message_id,
    +            session_key=session_key,
    +            choices=emoji_choices,
    +            on_choice_selected=on_choice_selected,
    +            requester_user_id=str((metadata or {}).get("requester_user_id") or "") or None,
    +            expires_at=time.monotonic() + max(self._approval_timeout_seconds, 0),
    +        )
    +        self._choice_picker_prompts_by_event[result.message_id] = prompt
    +
    +        for emoji in emoji_choices:
    +            try:
    +                reaction_event_id = await self._send_reaction(chat_id, result.message_id, emoji)
    +                if reaction_event_id:
    +                    prompt.bot_reaction_events[emoji] = str(reaction_event_id)
    +            except Exception as exc:
    +                logger.debug("Matrix: failed to add choice picker reaction %s: %s", emoji, exc)
    +
    +        return result
    +
         def format_message(self, content: str) -> str:
             """Pass-through — Matrix supports standard Markdown natively."""
             # Strip image markdown; media is uploaded separately.
    ```

    ```diff
    @@ -3328,6 +3412,40 @@ async def _on_reaction(self, event: Any) -> None:
                         )
                     return
     
    +            choice_prompt = self._choice_picker_prompts_by_event.get(reacts_to)
    +            if choice_prompt and not choice_prompt.resolved:
    +                if room_id != choice_prompt.chat_id:
    +                    return
    +                if self._matrix_prompt_expired(choice_prompt):
    +                    self._choice_picker_prompts_by_event.pop(reacts_to, None)
    +                    return
    +                if not await self._validate_matrix_prompt_reactor(
    +                    room_id, reacts_to, sender, choice_prompt, "choice picker"
    +                ):
    +                    return
    +                value = choice_prompt.choices.get(key)
    +                if value is None:
    +                    await self._send_invalid_reaction_feedback(
    +                        room_id,
    +                        reacts_to,
    +                        "That reaction is not one of the available choices.",
    +                    )
    +                    return
    +                choice_prompt.resolved = True
    +                self._choice_picker_prompts_by_event.pop(reacts_to, None)
    +                try:
    +                    confirmation = await choice_prompt.on_choice_selected(room_id, value)
    +                    if confirmation:
    +                        await self.send(room_id, confirmation, reply_to=reacts_to)
    +                except Exception as exc:
    +                    logger.error("Failed to apply choice from Matrix reaction: %s", exc)
    +                    await self.send(
    +                        room_id,
    +                        f"Failed to apply selection: {exc}",
    +                        reply_to=reacts_to,
    +                    )
    +                return
    +
         def _matrix_prompt_expired(self, prompt: Any) -> bool:
             expires_at = getattr(prompt, "expires_at", None)
             return expires_at is not None and time.monotonic() > float(expires_at)
    ```
  - `plugins/platforms/telegram/adapter.py`

    ```diff
    @@ -778,6 +778,7 @@ def __init__(self, config: PlatformConfig):
             )
             # Interactive model picker state per chat
             self._model_picker_state: Dict[str, dict] = {}
    +        self._choice_picker_state: Dict[str, dict] = {}
             # Approval button state: message_id → session_key
             self._approval_state: Dict[int, str] = {}
             # Slash-confirm button state: confirm_id → session_key (for /reload-mcp
    ```

    ```diff
    @@ -5213,6 +5214,128 @@ def get_label(slug):
                 return SendResult(success=False, error=_redact_telegram_error_text(e))
     
         _PROVIDER_PAGE_SIZE = 10
    +
    +    async def send_choice_picker(
    +        self,
    +        chat_id: str,
    +        title: str,
    +        choices: list,
    +        session_key: str,
    +        on_choice_selected,
    +        metadata: Optional[Dict[str, Any]] = None,
    +    ) -> SendResult:
    +        """Send a flat inline-keyboard choice picker (one tap → one value).
    +
    +        Generic single-level companion to ``send_model_picker`` used by
    +        `/reasoning`, `/fast`, and any future finite-choice command. Each
    +        choice dict: ``{"value": str, "label": str, "is_current": bool}``.
    +        """
    +        if not self._bot:
    +            return SendResult(success=False, error="Not connected")
    +
    +        try:
    +            buttons = []
    +            for i, choice in enumerate(choices):
    +                label = str(choice.get("label") or choice.get("value") or "")
    +                if choice.get("is_current"):
    +                    label = f"✓ {label}"
    +                buttons.append(
    +                    InlineKeyboardButton(label, callback_data=f"cp:{i}")
    +                )
    +            if not buttons:
    +                return SendResult(success=False, error="No choices")
    +            # Two buttons per row keeps labels readable on mobile.
    +            keyboard = InlineKeyboardMarkup(
    +                [buttons[i:i + 2] for i in range(0, len(buttons), 2)]
    +            )
    +
    +            thread_id = metadata.get("thread_id") if metadata else None
    +            reply_to_id = self._reply_to_message_id_for_send(None, metadata, reply_to_mode=self._reply_to_mode)
    +            msg = await self._send_message_with_thread_fallback(
    +                chat_id=normalize_telegram_chat_id(chat_id),
    +                text=self.format_message(title),
    +                parse_mode=ParseMode.MARKDOWN_V2,
    +                reply_markup=keyboard,
    +                reply_to_message_id=reply_to_id,
    +                **self._thread_kwargs_for_send(
    +                    chat_id,
    +                    thread_id,
    +                    metadata,
    +                    reply_to_message_id=reply_to_id,
    +                    reply_to_mode=self._reply_to_mode
    +                ),
    +                **self._link_preview_kwargs(),
    +            )
    +
    +            self._choice_picker_state[str(chat_id)] = {
    +                "msg_id": msg.message_id,
    +                "choices": choices,
    +                "session_key": session_key,
    +                "on_choice_selected": on_choice_selected,
    +            }
    +            return SendResult(success=True, message_id=str(msg.message_id))
    +        except Exception as e:
    +            logger.warning("[%s] send_choice_picker failed: %s", self.name, _redact_telegram_error_text(e))
    +            return SendResult(success=False, error=_redact_telegram_error_text(e))
    +
    +    async def _handle_choice_picker_callback(
    +        self, query, data: str, chat_id: str
    +    ) -> None:
    +        """Handle choice picker button taps (cp:<index>)."""
    +        state = self._choice_picker_state.get(chat_id)
    +        if not state:
    +            await query.answer(text="Picker expired — run the command again.")
    +            return
    +
    +        # Same authorization gate as approval buttons: unauthorized users in a
    +        # shared group must not flip session/config state via someone else's
    +        # picker message.
    +        query_message = getattr(query, "message", None)
    +        query_chat = getattr(query_message, "chat", None)
    +        if not self._is_callback_user_authorized(
    +            str(getattr(query.from_user, "id", "")),
    +            chat_id=getattr(query_message, "chat_id", None),
    +            chat_type=str(getattr(query_chat, "type", None)) if getattr(query_chat, "type", None) is not None else None,
    +            thread_id=str(getattr(query_message, "message_thread_id", None)) if getattr(query_message, "message_thread_id", None) is not None else None,
    +            user_name=getattr(query.from_user, "first_name", None),
    +        ):
    +            await query.answer(text="⛔ You are not authorized to change this setting.")
    +            return
    +
    +        try:
    +            idx = int(data[3:])
    +            choice = state["choices"][idx]
    +        except (ValueError, IndexError):
    +            await query.answer(text="Invalid selection.")
    +            return
    +
    +        callback = state.get("on_choice_selected")
    +        if not callback:
    +            await query.answer(text="Picker expired.")
    +            return
    +
    +        try:
    +            result_text = await callback(chat_id, str(choice.get("value") or ""))
    +        except Exception as exc:
    +            logger.error("Choice picker selection failed: %s", exc)
    +            result_text = f"Error applying selection: {exc}"
    +
    +        try:
    +            await query.edit_message_text(
    +                text=self.format_message(result_text),
    +                parse_mode=ParseMode.MARKDOWN_V2,
    +                reply_markup=None,
    +            )
    +        except Exception:
    +            try:
    +                await query.edit_message_text(
    +                    text=result_text, parse_mode=None, reply_markup=None,
    +                )
    +            except Exception:
    +                pass
    +        await query.answer()
    +        self._choice_picker_state.pop(chat_id, None)
    +
         _MODEL_PAGE_SIZE = 8
     
         def _build_provider_keyboard(self, providers: list, page: int = 0) -> tuple:
    ```

    ```diff
    @@ -5709,6 +5832,13 @@ async def _handle_callback_query(
                     await self._handle_model_picker_callback(query, data, chat_id)
                 return
     
    +        # --- Generic choice picker callbacks (/reasoning, /fast) ---
    +        if data.startswith("cp:"):
    +            chat_id = str(query.message.chat_id) if query.message else None
    +            if chat_id:
    +                await self._handle_choice_picker_callback(query, data, chat_id)
    +            return
    +
             # --- Gmail-triage callbacks (gt:verb:arg) ---
             if data.startswith("gt:"):
                 await self._handle_gmail_triage_callback(
    ```
  - `tests/gateway/test_choice_picker.py`

    ```diff
    @@ -0,0 +1,224 @@
    +"""Tests for the gateway interactive choice picker (/reasoning, /fast).
    +
    +The picker mirrors the /model picker architecture: the gateway gates on the
    +adapter *type* exposing ``send_choice_picker``, sends a flat choice list, and
    +falls back to the text status card when the platform has no picker or the
    +send fails. Selection flows through the same application path as the typed
    +command, so picker and typed arguments can never diverge.
    +"""
    +
    +import asyncio
    +from unittest.mock import AsyncMock, MagicMock
    +
    +import pytest
    +import yaml
    +
    +import gateway.run as gateway_run
    +from gateway.config import Platform
    +from gateway.platforms.base import MessageEvent, SendResult
    +from gateway.session import SessionSource
    +
    +
    +def _make_event(text="/reasoning", platform=Platform.TELEGRAM, user_id="12345", chat_id="67890"):
    +    source = SessionSource(
    +        platform=platform,
    +        user_id=user_id,
    +        chat_id=chat_id,
    +        user_name="testuser",
    +    )
    +    return MessageEvent(text=text, source=source)
    +
    +
    +class _PickerAdapter:
    +    """Adapter whose *type* exposes ``send_choice_picker`` (the gate the
    +    handler checks via ``getattr(type(adapter), 'send_choice_picker', None)``)."""
    +
    +    def __init__(self, success=True):
    +        self.calls = []
    +        self._success = success
    +
    +    async def send_choice_picker(self, **kwargs):
    +        self.calls.append(kwargs)
    +        return SendResult(success=self._success, message_id="m1")
    +
    +
    +class _NoPickerAdapter:
    +    """Adapter with no choice-picker capability."""
    +
    +
    +def _make_runner(adapter=None):
    +    runner = object.__new__(gateway_run.GatewayRunner)
    +    runner.adapters = {}
    +    runner._ephemeral_system_prompt = ""
    +    runner._prefill_messages = []
    +    runner._reasoning_config = None
    +    runner._session_reasoning_overrides = {}
    +    runner._show_reasoning = False
    +    runner._provider_routing = {}
    +    runner._fallback_model = None
    +    runner._running_agents = {}
    +    runner.hooks = MagicMock()
    +    runner.hooks.emit = AsyncMock()
    +    runner.hooks.loaded_hooks = []
    +    runner._session_db = None
    +    runner._get_or_create_gateway_honcho = lambda session_key: (None, None)
    +    runner._adapter_for_source = lambda source: adapter
    +    runner._thread_metadata_for_source = lambda source, anchor=None: {}
    +    runner._reply_anchor_for_event = lambda event: None
    +    return runner
    +
    +
    +class TestReasoningChoicePicker:
    +    @pytest.mark.asyncio
    +    async def test_bare_reasoning_sends_picker_when_adapter_supports_it(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +        adapter = _PickerAdapter()
    +        runner = _make_runner(adapter)
    +
    +        result = await runner._handle_reasoning_command(_make_event("/reasoning"))
    +
    +        assert result is None  # picker sent — adapter owns the response
    +        assert len(adapter.calls) == 1
    +        call = adapter.calls[0]
    +        values = [c["value"] for c in call["choices"]]
    +        # Full canonical ladder + none + subcommands, in order
    +        from hermes_constants import VALID_REASONING_EFFORTS
    +        assert values[0] == "none"
    +        assert values[1:1 + len(VALID_REASONING_EFFORTS)] == list(VALID_REASONING_EFFORTS)
    +        assert values[-3:] == ["reset", "show", "hide"]
    +
    +    @pytest.mark.asyncio
    +    async def test_bare_reasoning_falls_back_to_text_without_picker(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +        runner = _make_runner(_NoPickerAdapter())
    +
    +        result = await runner._handle_reasoning_command(_make_event("/reasoning"))
    +
    +        assert isinstance(result, str)
    +        assert "/reasoning" in result  # text status card
    +
    +    @pytest.mark.asyncio
    +    async def test_bare_reasoning_falls_back_to_text_when_picker_send_fails(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +        adapter = _PickerAdapter(success=False)
    +        runner = _make_runner(adapter)
    +
    +        result = await runner._handle_reasoning_command(_make_event("/reasoning"))
    +
    +        assert isinstance(result, str)
    +        assert len(adapter.calls) == 1  # attempted, then fell back
    +
    +    @pytest.mark.asyncio
    +    async def test_typed_argument_never_sends_picker(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +        adapter = _PickerAdapter()
    +        runner = _make_runner(adapter)
    +
    +        result = await runner._handle_reasoning_command(_make_event("/reasoning high"))
    +
    +        assert isinstance(result, str)
    +        assert adapter.calls == []
    +
    +    @pytest.mark.asyncio
    +    async def test_picker_selection_applies_same_as_typed(self, tmp_path, monkeypatch):
    +        """The picker's on_choice_selected must produce the identical state
    +        change as typing the argument (single application path)."""
    +        monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +        adapter = _PickerAdapter()
    +        runner = _make_runner(adapter)
    +        event = _make_event("/reasoning")
    +        session_key = runner._session_key_for_source(event.source)
    +
    +        await runner._handle_reasoning_command(event)
    +        on_choice = adapter.calls[0]["on_choice_selected"]
    +
    +        reply = await on_choice(event.source.chat_id, "ultra")
    +
    +        assert "ultra" in reply
    +        override = runner._session_reasoning_overrides.get(session_key)
    +        assert override == {"enabled": True, "effort": "ultra"}
    +
    +    @pytest.mark.asyncio
    +    async def test_picker_selection_of_current_level_marks_is_current(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +        (tmp_path / "config.yaml").write_text(
    +            yaml.safe_dump({"agent": {"reasoning_effort": "xhigh"}}), encoding="utf-8"
    +        )
    +        adapter = _PickerAdapter()
    +        runner = _make_runner(adapter)
    +
    +        await runner._handle_reasoning_command(_make_event("/reasoning"))
    +
    +        current = [c["value"] for c in adapter.calls[0]["choices"] if c.get("is_current")]
    +        assert current == ["xhigh"]
    +
    +    @pytest.mark.asyncio
    +    async def test_picker_show_choice_toggles_display(self, tmp_path, monkeypatch):
    +        monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +        adapter = _PickerAdapter()
    +        runner = _make_runner(adapter)
    +        event = _make_event("/reasoning")
    +
    +        await runner._handle_reasoning_command(event)
    +        on_choice = adapter.calls[0]["on_choice_selected"]
    +        await on_choice(event.source.chat_id, "show")
    +
    +        assert runner._show_reasoning is True
    +        saved = yaml.safe_load((tmp_path / "config.yaml").read_text(encoding="utf-8"))
    +        assert saved["display"]["platforms"]["telegram"]["show_reasoning"] is True
    +
    +
    +class TestFastChoicePicker:
    +    def _patch_fast_support(self, monkeypatch, tmp_path):
    +        monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +        monkeypatch.setattr(gateway_run, "_load_gateway_config", lambda: {})
    +        monkeypatch.setattr(gateway_run, "_resolve_gateway_model", lambda cfg: "gpt-5.6")
    +        import hermes_cli.models as models_mod
    +        monkeypatch.setattr(models_mod, "model_supports_fast_mode", lambda m: True)
    +
    +    @pytest.mark.asyncio
    +    async def test_bare_fast_sends_picker_when_adapter_supports_it(self, tmp_path, monkeypatch):
    +        self._patch_fast_support(monkeypatch, tmp_path)
    +        adapter = _PickerAdapter()
    +        runner = _make_runner(adapter)
    +
    +        result = await runner._handle_fast_command(_make_event("/fast"))
    +
    +        assert result is None
    +        values = [c["value"] for c in adapter.calls[0]["choices"]]
    +        assert values == ["fast", "normal"]
    +
    +    @pytest.mark.asyncio
    +    async def test_fast_picker_selection_persists_service_tier(self, tmp_path, monkeypatch):
    +        self._patch_fast_support(monkeypatch, tmp_path)
    +        adapter = _PickerAdapter()
    +        runner = _make_runner(adapter)
    +        event = _make_event("/fast")
    +
    +        await runner._handle_fast_command(event)
    +        on_choice = adapter.calls[0]["on_choice_selected"]
    +        await on_choice(event.source.chat_id, "fast")
    +
    +        assert runner._service_tier == "priority"
    +        saved = yaml.safe_load((tmp_path / "config.yaml").read_text(encoding="utf-8"))
    +        assert saved["agent"]["service_tier"] == "fast"
    +
    +    @pytest.mark.asyncio
    +    async def test_bare_fast_falls_back_to_text_without_picker(self, tmp_path, monkeypatch):
    +        self._patch_fast_support(monkeypatch, tmp_path)
    +        runner = _make_runner(_NoPickerAdapter())
    +
    +        result = await runner._handle_fast_command(_make_event("/fast"))
    +
    +        assert isinstance(result, str)
    +
    +    @pytest.mark.asyncio
    +    async def test_typed_fast_argument_never_sends_picker(self, tmp_path, monkeypatch):
    +        self._patch_fast_support(monkeypatch, tmp_path)
    +        adapter = _PickerAdapter()
    +        runner = _make_runner(adapter)
    +
    +        result = await runner._handle_fast_command(_make_event("/fast normal"))
    +
    +        assert isinstance(result, str)
    +        assert adapter.calls == []
    ```
- **#64026: feat(telegram): add reasoning and fast pickers**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#61110`
  - files: `gateway/slash_commands.py`, `plugins/platforms/telegram/adapter.py`, `tests/gateway/test_telegram_reasoning_fast_picker.py`, `website/docs/user-guide/messaging/telegram.md`, `website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/messaging/telegram.md`

## Fix status

- Merged to main in PR #65799 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-16`.
- Subsystem: `gateway`
