{"repo":"NousResearch/hermes-agent","public_id":"30582","display_label":"Inline pickers for /reasoning and /fast commands","summary":"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.","subsystem":"gateway","counts":{"members":4,"open_issues":1,"open_issues_distinct":1,"open_prs":1,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":65799,"merged_at":"2026-07-16T17:38:31+00:00"},"resolution":{"state":"partially_resolved","by":"pr-65799"},"freshness":{"state":"verified","last_verified_at":"2026-07-16T17:38:31+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.3933333333333333},"fix_edges":[{"issue_number":61110,"pr_number":65799,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-07-16T17:38:31+00:00"}],"members":[{"kind":"issue","number":30582,"title":"Feature: Telegram inline keyboard buttons for slash commands with discrete options","url":null,"state":"open"},{"kind":"issue","number":61110,"title":"Feature: Telegram inline pickers for /reasoning and /fast (parity with /model)","url":null,"state":"closed"},{"kind":"pr","number":64026,"title":"feat(telegram): add reasoning and fast pickers","url":null,"state":"open","close_reason":{"refs":[{"kind":"pr","value":"#61110","number":61110}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":65799,"title":"feat(gateway): inline choice pickers for /reasoning and /fast across Telegram, Discord, Matrix","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["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","plugins/platforms/telegram/adapter.py","tests/gateway/test_choice_picker.py"],"fix_hunks":[{"file":"gateway/platforms/ADDING_A_PLATFORM.md","hunks":["@@ -120,6 +120,7 @@ If your platform supports interactive button/menu messages, implement these for\n | `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`. |\n | `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`. |\n | `send_model_picker(...)` | Interactive `/model` picker. Used by Telegram and Discord. |\n+| `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. |\n \n 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.\n "]},{"file":"gateway/slash_commands.py","hunks":["@@ -2704,7 +2704,154 @@ async def _handle_background_command(self, event: MessageEvent) -> str:\n         preview = prompt[:60] + (\"...\" if len(prompt) > 60 else \"\")\n         return t(\"gateway.background.started\", preview=preview, task_id=task_id)\n \n-    async def _handle_reasoning_command(self, event: MessageEvent) -> str:\n+    def _save_gateway_config_key(self, key_path: str, value) -> bool:\n+        \"\"\"Save a dot-separated key to config.yaml (shared by /reasoning, /fast\n+        and their interactive pickers).\"\"\"\n+        import yaml\n+        from gateway.run import _hermes_home\n+        config_path = _hermes_home / \"config.yaml\"\n+        try:\n+            user_config = {}\n+            if config_path.exists():\n+                with open(config_path, encoding=\"utf-8\") as f:\n+                    user_config = yaml.safe_load(f) or {}\n+            keys = key_path.split(\".\")\n+            current = user_config\n+            for k in keys[:-1]:\n+                if k not in current or not isinstance(current[k], dict):\n+                    current[k] = {}\n+                current = current[k]\n+            current[keys[-1]] = value\n+            atomic_config_write(config_path, user_config)\n+            return True\n+        except Exception as e:\n+            logger.error(\"Failed to save config key %s: %s\", key_path, e)\n+            return False\n+\n+    def _apply_reasoning_selection(\n+        self,\n+        session_key: str,\n+        platform_key: str,\n+        value: str,\n+        persist_global: bool = False,\n+    ) -> str:\n+        \"\"\"Apply a /reasoning argument (typed or picked) and return the reply.\n+\n+        Single application path shared by the typed `/reasoning <arg>` branch\n+        and the interactive choice picker, so both surfaces stay in lockstep\n+        with the canonical parser.\n+        \"\"\"\n+        from hermes_constants import parse_reasoning_effort\n+\n+        value = (value or \"\").strip().lower()\n+\n+        # Display toggle (per-platform)\n+        if value in {\"show\", \"on\"}:\n+            self._show_reasoning = True\n+            self._save_gateway_config_key(\n+                f\"display.platforms.{platform_key}.show_reasoning\", True\n+            )\n+            return t(\"gateway.reasoning.display_set_on\", platform=platform_key)\n+        if value in {\"hide\", \"off\"}:\n+            self._show_reasoning = False\n+            self._save_gateway_config_key(\n+                f\"display.platforms.{platform_key}.show_reasoning\", False\n+            )\n+            return t(\"gateway.reasoning.display_set_off\", platform=platform_key)\n+\n+        if value == \"reset\":\n+            if persist_global:\n+                return t(\"gateway.reasoning.reset_global_unsupported\")\n+            self._set_session_reasoning_override(session_key, None)\n+            self._reasoning_config = self._load_reasoning_config()\n+            self._evict_cached_agent(session_key)\n+            return t(\"gateway.reasoning.reset_done\")\n+\n+        parsed = parse_reasoning_effort(value)\n+        if parsed is None:\n+            return t(\"gateway.reasoning.unknown_arg\", arg=value)\n+\n+        self._reasoning_config = parsed\n+        if persist_global:\n+            if self._save_gateway_config_key(\"agent.reasoning_effort\", value):\n+                self._set_session_reasoning_override(session_key, None)\n+                self._evict_cached_agent(session_key)\n+                return t(\"gateway.reasoning.set_global\", effort=value)\n+            self._set_session_reasoning_override(session_key, parsed)\n+            self._evict_cached_agent(session_key)\n+            return t(\"gateway.reasoning.set_global_save_failed\", effort=value)\n+\n+        self._set_session_reasoning_override(session_key, parsed)\n+        self._evict_cached_agent(session_key)\n+        return t(\"gateway.reasoning.set_session\", effort=value)\n+\n+    def _reasoning_picker_choices(self, current_effort: str) -> list:\n+        \"\"\"Build the choice list for the interactive /reasoning picker.\"\"\"\n+        from hermes_constants import VALID_REASONING_EFFORTS\n+\n+        choices = [\n+            {\n+                \"value\": \"none\",\n+                \"label\": t(\"gateway.reasoning.choice_none\"),\n+                \"is_current\": current_effort == \"none\",\n+            }\n+        ]\n+        for level in VALID_REASONING_EFFORTS:\n+            choices.append(\n+                {\n+                    \"value\": level,\n+                    \"label\": level,\n+                    \"is_current\": level == current_effort,\n+                }\n+            )\n+        choices.extend(\n+            [\n+                {\"value\": \"reset\", \"label\": t(\"gateway.reasoning.choice_reset\"), \"is_current\": False},\n+                {\"value\": \"show\", \"label\": t(\"gateway.reasoning.choice_show\"), \"is_current\": False},\n+                {\"value\": \"hide\", \"label\": t(\"gateway.reasoning.choice_hide\"), \"is_current\": False},\n+            ]\n+        )\n+        return choices\n+\n+    async def _try_send_choice_picker(\n+        self,\n+        event: MessageEvent,\n+        session_key: str,\n+        title: str,\n+        choices: list,\n+        on_choice_selected,\n+    ) -> bool:\n+        \"\"\"Send an interactive choice picker when the platform supports it.\n+\n+        Mirrors the `/model` picker gate: the capability is detected on the\n+        adapter *type* (``send_choice_picker``), and a failed send falls back\n+        to the text path (returns False) instead of erroring the command.\n+        \"\"\"\n+        adapter = getattr(self, \"_adapter_for_source\")(event.source)\n+        has_picker = (\n+            adapter is not None\n+            and getattr(type(adapter), \"send_choice_picker\", None) is not None\n+        )\n+        if not has_picker:\n+            return False\n+        try:\n+            metadata = self._thread_metadata_for_source(\n+                event.source, self._reply_anchor_for_event(event)\n+            )\n+            result = await adapter.send_choice_picker(\n+                chat_id=event.source.chat_id,\n+                title=title,\n+                choices=choices,\n+                session_key=session_key,\n+                on_choice_selected=on_choice_selected,\n+                metadata=metadata,\n+            )\n+            return bool(getattr(result, \"success\", False))\n+        except Exception as e:\n+            logger.warning(\"send_choice_picker failed, falling back to text: %s\", e)\n+            return False\n+\n+    async def _handle_reasoning_command(self, event: MessageEvent) -> Optional[str]:\n         \"\"\"Handle /reasoning command — manage reasoning effort and display toggle.\n \n         Usage:","@@ -2715,13 +2862,10 @@ async def _handle_reasoning_command(self, event: MessageEvent) -> str:\n             /reasoning show|on               Show model reasoning in responses\n             /reasoning hide|off              Hide model reasoning from responses\n         \"\"\"\n-        from gateway.run import _hermes_home, _platform_config_key\n-        from hermes_constants import parse_reasoning_effort\n-        import yaml\n+        from gateway.run import _platform_config_key\n \n         raw_args = event.get_command_args().strip()\n         args, persist_global = self._parse_reasoning_command_args(raw_args)\n-        config_path = _hermes_home / \"config.yaml\"\n         # Normalize the source (Telegram DM topic recovery) before deriving\n         # the override key so storage matches the key the next message turn\n         # reads — same fix as /model (#30479).","@@ -2739,35 +2883,18 @@ async def _handle_reasoning_command(self, event: MessageEvent) -> str:\n             model=_session_model,\n         )\n \n-        def _save_config_key(key_path: str, value):\n-            \"\"\"Save a dot-separated key to config.yaml.\"\"\"\n-            try:\n-                user_config = {}\n-                if config_path.exists():\n-                    with open(config_path, encoding=\"utf-8\") as f:\n-                        user_config = yaml.safe_load(f) or {}\n-                keys = key_path.split(\".\")\n-                current = user_config\n-                for k in keys[:-1]:\n-                    if k not in current or not isinstance(current[k], dict):\n-                        current[k] = {}\n-                    current = current[k]\n-                current[keys[-1]] = value\n-                atomic_config_write(config_path, user_config)\n-                return True\n-            except Exception as e:\n-                logger.error(\"Failed to save config key %s: %s\", key_path, e)\n-                return False\n-\n         if not raw_args:\n             # Show current state\n             rc = self._reasoning_config\n             if rc is None:\n                 level = t(\"gateway.reasoning.level_default\")\n+                current_effort = \"medium\"\n             elif rc.get(\"enabled\") is False:\n                 level = t(\"gateway.reasoning.level_disabled\")\n+                current_effort = \"none\"\n             else:\n                 level = rc.get(\"effort\", \"medium\")\n+                current_effort = level\n             display_state = (\n                 t(\"gateway.reasoning.display_on\")\n                 if self._show_reasoning","@@ -2779,6 +2906,31 @@ def _save_config_key(key_path: str, value):\n                 if has_session_override\n                 else t(\"gateway.reasoning.scope_global\")\n             )\n+\n+            # Interactive picker on platforms that support it (parity with the\n+            # /model picker). Falls through to the text status card otherwise.\n+            _picker_platform_key = _platform_config_key(event.source.platform)\n+\n+            async def _on_reasoning_choice(_chat_id: str, value: str) -> str:\n+                return self._apply_reasoning_selection(\n+                    session_key, _picker_platform_key, value\n+                )\n+\n+            picker_sent = await self._try_send_choice_picker(\n+                event,\n+                session_key,\n+                title=t(\n+                    \"gateway.reasoning.picker_title\",\n+                    level=level,\n+                    scope=scope,\n+                    display=display_state,\n+                ),\n+                choices=self._reasoning_picker_choices(current_effort),\n+                on_choice_selected=_on_reasoning_choice,\n+            )\n+            if picker_sent:\n+                return None  # Picker sent — adapter handles the response\n+\n             return t(\n                 \"gateway.reasoning.status\",\n                 level=level,","@@ -2786,47 +2938,11 @@ def _save_config_key(key_path: str, value):\n                 display=display_state,\n             )\n \n-        # Display toggle (per-platform)\n+        # Typed argument path — same applier the picker uses.\n         platform_key = _platform_config_key(event.source.platform)\n-        if args in {\"show\", \"on\"}:\n-            self._show_reasoning = True\n-            _save_config_key(f\"display.platforms.{platform_key}.show_reasoning\", True)\n-            return t(\"gateway.reasoning.display_set_on\", platform=platform_key)\n-\n-        if args in {\"hide\", \"off\"}:\n-            self._show_reasoning = False\n-            _save_config_key(f\"display.platforms.{platform_key}.show_reasoning\", False)\n-            return t(\"gateway.reasoning.display_set_off\", platform=platform_key)\n-\n-        # Effort level change\n-        effort = args.strip()\n-        if effort == \"reset\":\n-            if persist_global:\n-                return t(\"gateway.reasoning.reset_global_unsupported\")\n-            self._set_session_reasoning_override(session_key, None)\n-            self._reasoning_config = self._load_reasoning_config()\n-            self._evict_cached_agent(session_key)\n-            return t(\"gateway.reasoning.reset_done\")\n-        parsed = parse_reasoning_effort(effort)\n-        if parsed is None:\n-            return t(\n-                \"gateway.reasoning.unknown_arg\",\n-                arg=effort or raw_args.lower(),\n-            )\n-\n-        self._reasoning_config = parsed\n-        if persist_global:\n-            if _save_config_key(\"agent.reasoning_effort\", effort):\n-                self._set_session_reasoning_override(session_key, None)\n-                self._evict_cached_agent(session_key)\n-                return t(\"gateway.reasoning.set_global\", effort=effort)\n-            self._set_session_reasoning_override(session_key, parsed)\n-            self._evict_cached_agent(session_key)\n-            return t(\"gateway.reasoning.set_global_save_failed\", effort=effort)\n-\n-        self._set_session_reasoning_override(session_key, parsed)\n-        self._evict_cached_agent(session_key)\n-        return t(\"gateway.reasoning.set_session\", effort=effort)\n+        return self._apply_reasoning_selection(\n+            session_key, platform_key, args, persist_global=persist_global\n+        )\n \n     async def _handle_memory_command(self, event: MessageEvent) -> str:\n         \"\"\"Handle /memory — review pending memory writes + toggle the approval gate.","@@ -2932,14 +3048,12 @@ def _set_approval(enabled: bool):\n                      f\"~/.hermes/pending/skills/{pending_id}.json)\")\n         return out\n \n-    async def _handle_fast_command(self, event: MessageEvent) -> str:\n+    async def _handle_fast_command(self, event: MessageEvent) -> Optional[str]:\n         \"\"\"Handle /fast — mirror the CLI Priority Processing toggle in gateway chats.\"\"\"\n-        from gateway.run import _hermes_home, _load_gateway_config, _resolve_gateway_model\n-        import yaml\n+        from gateway.run import _load_gateway_config, _resolve_gateway_model\n         from hermes_cli.models import model_supports_fast_mode\n \n         args = event.get_command_args().strip().lower()\n-        config_path = _hermes_home / \"config.yaml\"\n         self._service_tier = self._load_service_tier()\n \n         user_config = _load_gateway_config()","@@ -2947,44 +3061,56 @@ async def _handle_fast_command(self, event: MessageEvent) -> str:\n         if not model_supports_fast_mode(model):\n             return t(\"gateway.fast.not_supported\")\n \n-        def _save_config_key(key_path: str, value):\n-            \"\"\"Save a dot-separated key to config.yaml.\"\"\"\n-            try:\n-                user_config = {}\n-                if config_path.exists():\n-                    with open(config_path, encoding=\"utf-8\") as f:\n-                        user_config = yaml.safe_load(f) or {}\n-                keys = key_path.split(\".\")\n-                current = user_config\n-                for k in keys[:-1]:\n-                    if k not in current or not isinstance(current[k], dict):\n-                        current[k] = {}\n-                    current = current[k]\n-                current[keys[-1]] = value\n-                atomic_config_write(config_path, user_config)\n-                return True\n-            except Exception as e:\n-                logger.error(\"Failed to save config key %s: %s\", key_path, e)\n-                return False\n+        def _apply_fast_selection(value: str) -> str:\n+            \"\"\"Apply a /fast argument (typed or picked) and return the reply.\"\"\"\n+            if value in {\"fast\", \"on\"}:\n+                self._service_tier = \"priority\"\n+                saved_value = \"fast\"\n+                label = t(\"gateway.fast.label_fast\")\n+            elif value in {\"normal\", \"off\"}:\n+                self._service_tier = None\n+                saved_value = \"normal\"\n+                label = t(\"gateway.fast.label_normal\")\n+            else:\n+                return t(\"gateway.fast.unknown_arg\", arg=value)\n+            if self._save_gateway_config_key(\"agent.service_tier\", saved_value):\n+                return t(\"gateway.fast.saved\", label=label)\n+            return t(\"gateway.fast.session_only\", label=label)\n \n         if not args or args == \"status\":\n-            status = t(\"gateway.fast.status_fast\") if self._service_tier == \"priority\" else t(\"gateway.fast.status_normal\")\n-            return t(\"gateway.fast.status\", mode=status)\n+            is_fast = self._service_tier == \"priority\"\n+            status = t(\"gateway.fast.status_fast\") if is_fast else t(\"gateway.fast.status_normal\")\n \n-        if args in {\"fast\", \"on\"}:\n-            self._service_tier = \"priority\"\n-            saved_value = \"fast\"\n-            label = t(\"gateway.fast.label_fast\")\n-        elif args in {\"normal\", \"off\"}:\n-            self._service_tier = None\n-            saved_value = \"normal\"\n-            label = t(\"gateway.fast.label_normal\")\n-        else:\n-            return t(\"gateway.fast.unknown_arg\", arg=args)\n+            # Interactive picker on platforms that support it.\n+            session_key = self._session_key_for_source(event.source)\n+\n+            async def _on_fast_choice(_chat_id: str, value: str) -> str:\n+                return _apply_fast_selection(value)\n+\n+            picker_sent = await self._try_send_choice_picker(\n+                event,\n+                session_key,\n+                title=t(\"gateway.fast.picker_title\", mode=status),\n+                choices=[\n+                    {\n+                        \"value\": \"fast\",\n+                        \"label\": t(\"gateway.fast.choice_fast\"),\n+                        \"is_current\": is_fast,\n+                    },\n+                    {\n+                        \"value\": \"normal\",\n+                        \"label\": t(\"gateway.fast.choice_normal\"),\n+                        \"is_current\": not is_fast,\n+                    },\n+                ],\n+                on_choice_selected=_on_fast_choice,\n+            )\n+            if picker_sent:\n+                return None  # Picker sent — adapter handles the response\n+\n+            return t(\"gateway.fast.status\", mode=status)\n \n-        if _save_config_key(\"agent.service_tier\", saved_value):\n-            return t(\"gateway.fast.saved\", label=label)\n-        return t(\"gateway.fast.session_only\", label=label)\n+        return _apply_fast_selection(args)\n \n     async def _handle_yolo_command(self, event: MessageEvent) -> Union[str, EphemeralReply]:\n         \"\"\"Handle /yolo — toggle dangerous command approval bypass for this session only.\"\"\""]},{"file":"locales/af.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nHuidige modus: `{mode}`\\\\n\\\\nKies \\\\'n opsie:\"\n+    choice_fast:           \"fast — Priority Processing aan\"\n+    choice_normal:         \"normal — standaardverwerking\"\n \n   footer:\n     status:                \"📎 Looptyd-voetstuk: **{state}**\\nVelde: `{fields}`\\nPlatform: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` word nie ondersteun nie. Gebruik `/reasoning <level> --global` om die globale verstek te verander.\"\n     reset_done:                \"🧠 ✓ Sessie-redenering-oorskryf verwyder; val terug op globale konfigurasie.\"\n     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\"\n+    picker_title:              \"🧠 **Redenering-instellings**\\\\n\\\\n**Inspanning:** `{level}`\\\\n**Bereik:** {scope}\\\\n**Vertoon:** {display}\\\\n\\\\nKies \\\\'n opsie:\"\n+    choice_none:               \"none — deaktiveer redenering\"\n+    choice_reset:              \"reset — vee sessie-oorskrywing uit\"\n+    choice_show:               \"wys redenering in antwoorde\"\n+    choice_hide:               \"versteek redenering in antwoorde\"\n     set_global:                \"🧠 ✓ Redenering-inspanning gestel op `{effort}` (gestoor in konfigurasie)\\n_(neem effek by die volgende boodskap)_\"\n     set_global_save_failed:    \"🧠 ✓ Redenering-inspanning gestel op `{effort}` (slegs sessie — konfigurasie-stoor het misluk)\\n_(neem effek by die volgende boodskap)_\"\n     set_session:               \"🧠 ✓ Redenering-inspanning gestel op `{effort}` (slegs sessie — voeg `--global` by om permanent te stoor)\\n_(neem effek by die volgende boodskap)_\""]},{"file":"locales/de.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nAktueller Modus: `{mode}`\\\\n\\\\nOption wählen:\"\n+    choice_fast:           \"fast — Priority Processing an\"\n+    choice_normal:         \"normal — Standardverarbeitung\"\n \n   footer:\n     status:                \"📎 Laufzeit-Fußzeile: **{state}**\\nFelder: `{fields}`\\nPlattform: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` wird nicht unterstützt. Verwenden Sie `/reasoning <level> --global`, um den globalen Standard zu ändern.\"\n     reset_done:                \"🧠 ✓ Sitzungs-Reasoning-Override gelöscht; Rückfall auf globale Konfiguration.\"\n     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\"\n+    picker_title:              \"🧠 **Reasoning-Einstellungen**\\\\n\\\\n**Stärke:** `{level}`\\\\n**Geltungsbereich:** {scope}\\\\n**Anzeige:** {display}\\\\n\\\\nOption wählen:\"\n+    choice_none:               \"none — Reasoning deaktivieren\"\n+    choice_reset:              \"reset — Sitzungs-Override löschen\"\n+    choice_show:               \"Reasoning in Antworten anzeigen\"\n+    choice_hide:               \"Reasoning in Antworten ausblenden\"\n     set_global:                \"🧠 ✓ Reasoning-Stärke auf `{effort}` gesetzt (in Konfiguration gespeichert)\\n_(wird mit der nächsten Nachricht wirksam)_\"\n     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)_\"\n     set_session:               \"🧠 ✓ Reasoning-Stärke auf `{effort}` gesetzt (nur Sitzung — `--global` hinzufügen, um zu speichern)\\n_(wird mit der nächsten Nachricht wirksam)_\""]},{"file":"locales/en.yaml","hunks":["@@ -139,6 +139,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\n\\nCurrent mode: `{mode}`\\n\\nPick an option:\"\n+    choice_fast:           \"fast — Priority Processing on\"\n+    choice_normal:         \"normal — standard processing\"\n \n   footer:\n     status:                \"📎 Runtime footer: **{state}**\\nFields: `{fields}`\\nPlatform: `{platform}`\"","@@ -209,6 +212,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` is not supported. Use `/reasoning <level> --global` to change the global default.\"\n     reset_done:                \"🧠 ✓ Session reasoning override cleared; falling back to global config.\"\n     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\"\n+    picker_title:              \"🧠 **Reasoning Settings**\\n\\n**Effort:** `{level}`\\n**Scope:** {scope}\\n**Display:** {display}\\n\\nPick an option:\"\n+    choice_none:               \"none — disable reasoning\"\n+    choice_reset:              \"reset — clear session override\"\n+    choice_show:               \"show reasoning in replies\"\n+    choice_hide:               \"hide reasoning from replies\"\n     set_global:                \"🧠 ✓ Reasoning effort set to `{effort}` (saved to config)\\n_(takes effect on next message)_\"\n     set_global_save_failed:    \"🧠 ✓ Reasoning effort set to `{effort}` (session only — config save failed)\\n_(takes effect on next message)_\"\n     set_session:               \"🧠 ✓ Reasoning effort set to `{effort}` (session only — add `--global` to persist)\\n_(takes effect on next message)_\""]},{"file":"locales/es.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nModo actual: `{mode}`\\\\n\\\\nElige una opción:\"\n+    choice_fast:           \"fast — Priority Processing activado\"\n+    choice_normal:         \"normal — procesamiento estándar\"\n \n   footer:\n     status:                \"📎 Pie de ejecución: **{state}**\\nCampos: `{fields}`\\nPlataforma: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` no es compatible. Usa `/reasoning <level> --global` para cambiar el valor global por defecto.\"\n     reset_done:                \"🧠 ✓ Anulación de razonamiento de la sesión borrada; volviendo a la configuración global.\"\n     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\"\n+    picker_title:              \"🧠 **Ajustes de razonamiento**\\\\n\\\\n**Esfuerzo:** `{level}`\\\\n**Alcance:** {scope}\\\\n**Visualización:** {display}\\\\n\\\\nElige una opción:\"\n+    choice_none:               \"none — desactivar razonamiento\"\n+    choice_reset:              \"reset — borrar el ajuste de sesión\"\n+    choice_show:               \"mostrar razonamiento en respuestas\"\n+    choice_hide:               \"ocultar razonamiento en respuestas\"\n     set_global:                \"🧠 ✓ Esfuerzo de razonamiento ajustado a `{effort}` (guardado en la configuración)\\n_(se aplica en el próximo mensaje)_\"\n     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)_\"\n     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)_\""]},{"file":"locales/fr.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nMode actuel : `{mode}`\\\\n\\\\nChoisissez une option :\"\n+    choice_fast:           \"fast — Priority Processing activé\"\n+    choice_normal:         \"normal — traitement standard\"\n \n   footer:\n     status:                \"📎 Pied de page d'exécution : **{state}**\\nChamps : `{fields}`\\nPlateforme : `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` n'est pas pris en charge. Utilisez `/reasoning <level> --global` pour modifier la valeur globale par défaut.\"\n     reset_done:                \"🧠 ✓ Remplacement de raisonnement de la session effacé ; retour à la configuration globale.\"\n     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\"\n+    picker_title:              \"🧠 **Paramètres de raisonnement**\\\\n\\\\n**Effort :** `{level}`\\\\n**Portée :** {scope}\\\\n**Affichage :** {display}\\\\n\\\\nChoisissez une option :\"\n+    choice_none:               \"none — désactiver le raisonnement\"\n+    choice_reset:              \"reset — effacer le réglage de session\"\n+    choice_show:               \"afficher le raisonnement dans les réponses\"\n+    choice_hide:               \"masquer le raisonnement dans les réponses\"\n     set_global:                \"🧠 ✓ Effort de raisonnement défini sur `{effort}` (enregistré dans la configuration)\\n_(prend effet au prochain message)_\"\n     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)_\"\n     set_session:               \"🧠 ✓ Effort de raisonnement défini sur `{effort}` (session uniquement — ajoutez `--global` pour persister)\\n_(prend effet au prochain message)_\""]},{"file":"locales/ga.yaml","hunks":["@@ -128,6 +128,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nMód reatha: `{mode}`\\\\n\\\\nRoghnaigh rogha:\"\n+    choice_fast:           \"fast — Priority Processing ar siúl\"\n+    choice_normal:         \"normal — gnáthphróiseáil\"\n \n   footer:\n     status:                \"📎 Buntásc rite: **{state}**\\nRéimsí: `{fields}`\\nArdán: `{platform}`\"","@@ -198,6 +201,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ Ní thacaítear le `/reasoning reset --global`. Úsáid `/reasoning <level> --global` chun an réamhshocrú domhanda a athrú.\"\n     reset_done:                \"🧠 ✓ Sárú réasúnaíochta seisiúin glanta; ag titim siar ar an gcumraíocht dhomhanda.\"\n     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\"\n+    picker_title:              \"🧠 **Socruithe Réasúnaíochta**\\\\n\\\\n**Iarracht:** `{level}`\\\\n**Scóip:** {scope}\\\\n**Taispeáint:** {display}\\\\n\\\\nRoghnaigh rogha:\"\n+    choice_none:               \"none — díchumasaigh réasúnaíocht\"\n+    choice_reset:              \"reset — glan sárú an tseisiúin\"\n+    choice_show:               \"taispeáin réasúnaíocht sna freagraí\"\n+    choice_hide:               \"folaigh réasúnaíocht ó na freagraí\"\n     set_global:                \"🧠 ✓ Iarracht réasúnaíochta socraithe go `{effort}` (sábháilte sa chumraíocht)\\n_(éifeachtach ón gcéad teachtaireacht eile)_\"\n     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)_\"\n     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)_\""]},{"file":"locales/hu.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nJelenlegi mód: `{mode}`\\\\n\\\\nVálassz egy opciót:\"\n+    choice_fast:           \"fast — Priority Processing bekapcsolva\"\n+    choice_normal:         \"normal — normál feldolgozás\"\n \n   footer:\n     status:                \"📎 Futási idejű lábléc: **{state}**\\nMezők: `{fields}`\\nPlatform: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     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.\"\n     reset_done:                \"🧠 ✓ A munkamenet gondolkodási felülbírálása törölve; visszaállás a globális konfigurációra.\"\n     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\"\n+    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:\"\n+    choice_none:               \"none — gondolkodás kikapcsolása\"\n+    choice_reset:              \"reset — munkameneti felülbírálás törlése\"\n+    choice_show:               \"gondolkodás megjelenítése a válaszokban\"\n+    choice_hide:               \"gondolkodás elrejtése a válaszokból\"\n     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)_\"\n     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)_\"\n     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)_\""]},{"file":"locales/it.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nModalità attuale: `{mode}`\\\\n\\\\nScegli un\\\\'opzione:\"\n+    choice_fast:           \"fast — Priority Processing attivo\"\n+    choice_normal:         \"normal — elaborazione standard\"\n \n   footer:\n     status:                \"📎 Footer di runtime: **{state}**\\nCampi: `{fields}`\\nPiattaforma: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` non è supportato. Usa `/reasoning <level> --global` per cambiare il valore predefinito globale.\"\n     reset_done:                \"🧠 ✓ Override di reasoning della sessione cancellato; ripristino della configurazione globale.\"\n     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\"\n+    picker_title:              \"🧠 **Impostazioni di reasoning**\\\\n\\\\n**Sforzo:** `{level}`\\\\n**Ambito:** {scope}\\\\n**Visualizzazione:** {display}\\\\n\\\\nScegli un\\\\'opzione:\"\n+    choice_none:               \"none — disattiva il reasoning\"\n+    choice_reset:              \"reset — cancella l'override di sessione\"\n+    choice_show:               \"mostra il reasoning nelle risposte\"\n+    choice_hide:               \"nascondi il reasoning dalle risposte\"\n     set_global:                \"🧠 ✓ Sforzo di reasoning impostato su `{effort}` (salvato nella configurazione)\\n_(verrà applicato al prossimo messaggio)_\"\n     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)_\"\n     set_session:               \"🧠 ✓ Sforzo di reasoning impostato su `{effort}` (solo per questa sessione — aggiungi `--global` per renderlo permanente)\\n_(verrà applicato al prossimo messaggio)_\""]},{"file":"locales/ja.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\n現在のモード: `{mode}`\\\\n\\\\nオプションを選択:\"\n+    choice_fast:           \"fast — Priority Processing オン\"\n+    choice_normal:         \"normal — 標準処理\"\n \n   footer:\n     status:                \"📎 ランタイムフッター: **{state}**\\nフィールド: `{fields}`\\nプラットフォーム: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` はサポートされていません。グローバルのデフォルトを変更するには `/reasoning <level> --global` を使用してください。\"\n     reset_done:                \"🧠 ✓ セッションの推論オーバーライドをクリアしました。グローバル設定にフォールバックします。\"\n     unknown_arg:               \"⚠️ 不明な引数: `{arg}`\\n\\n**有効なレベル:** none, minimal, low, medium, high, xhigh, max, ultra\\n**表示:** show, hide\\n**永続化:** セッションを越えて保存するには `--global` を追加\"\n+    picker_title:              \"🧠 **推論設定**\\\\n\\\\n**強度:** `{level}`\\\\n**スコープ:** {scope}\\\\n**表示:** {display}\\\\n\\\\nオプションを選択:\"\n+    choice_none:               \"none — 推論を無効化\"\n+    choice_reset:              \"reset — セッション上書きをクリア\"\n+    choice_show:               \"返信に推論を表示\"\n+    choice_hide:               \"返信の推論を非表示\"\n     set_global:                \"🧠 ✓ 推論強度を `{effort}` に設定しました (設定に保存)\\n_(次のメッセージから有効)_\"\n     set_global_save_failed:    \"🧠 ✓ 推論強度を `{effort}` に設定しました (セッションのみ — 設定の保存に失敗)\\n_(次のメッセージから有効)_\"\n     set_session:               \"🧠 ✓ 推論強度を `{effort}` に設定しました (セッションのみ — 永続化するには `--global` を追加)\\n_(次のメッセージから有効)_\""]},{"file":"locales/ko.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\n현재 모드: `{mode}`\\\\n\\\\n옵션을 선택하세요:\"\n+    choice_fast:           \"fast — Priority Processing 켜기\"\n+    choice_normal:         \"normal — 표준 처리\"\n \n   footer:\n     status:                \"📎 런타임 푸터: **{state}**\\n필드: `{fields}`\\n플랫폼: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global`은 지원되지 않습니다. 전역 기본값을 변경하려면 `/reasoning <level> --global`을 사용하세요.\"\n     reset_done:                \"🧠 ✓ 세션 추론 재정의가 해제되었습니다. 전역 설정으로 돌아갑니다.\"\n     unknown_arg:               \"⚠️ 알 수 없는 인수: `{arg}`\\n\\n**유효한 수준:** none, minimal, low, medium, high, xhigh, max, ultra\\n**표시:** show, hide\\n**영구화:** 이 세션을 넘어 저장하려면 `--global`을 추가하세요\"\n+    picker_title:              \"🧠 **추론 설정**\\\\n\\\\n**노력:** `{level}`\\\\n**범위:** {scope}\\\\n**표시:** {display}\\\\n\\\\n옵션을 선택하세요:\"\n+    choice_none:               \"none — 추론 비활성화\"\n+    choice_reset:              \"reset — 세션 재정의 초기화\"\n+    choice_show:               \"응답에 추론 표시\"\n+    choice_hide:               \"응답에서 추론 숨기기\"\n     set_global:                \"🧠 ✓ 추론 노력이 `{effort}`(으)로 설정되었습니다 (설정에 저장됨)\\n_(다음 메시지부터 적용됩니다)_\"\n     set_global_save_failed:    \"🧠 ✓ 추론 노력이 `{effort}`(으)로 설정되었습니다 (세션 한정 — 설정 저장 실패)\\n_(다음 메시지부터 적용됩니다)_\"\n     set_session:               \"🧠 ✓ 추론 노력이 `{effort}`(으)로 설정되었습니다 (세션 한정 — 영구 저장하려면 `--global` 추가)\\n_(다음 메시지부터 적용됩니다)_\""]},{"file":"locales/pt.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nModo atual: `{mode}`\\\\n\\\\nEscolha uma opção:\"\n+    choice_fast:           \"fast — Priority Processing ativado\"\n+    choice_normal:         \"normal — processamento padrão\"\n \n   footer:\n     status:                \"📎 Rodapé de execução: **{state}**\\nCampos: `{fields}`\\nPlataforma: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` não é suportado. Usa `/reasoning <level> --global` para alterar o predefinido global.\"\n     reset_done:                \"🧠 ✓ Substituição de raciocínio da sessão removida; a regressar à configuração global.\"\n     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\"\n+    picker_title:              \"🧠 **Definições de raciocínio**\\\\n\\\\n**Esforço:** `{level}`\\\\n**Âmbito:** {scope}\\\\n**Visualização:** {display}\\\\n\\\\nEscolha uma opção:\"\n+    choice_none:               \"none — desativar raciocínio\"\n+    choice_reset:              \"reset — limpar o override de sessão\"\n+    choice_show:               \"mostrar raciocínio nas respostas\"\n+    choice_hide:               \"ocultar raciocínio das respostas\"\n     set_global:                \"🧠 ✓ Esforço de raciocínio definido como `{effort}` (guardado na configuração)\\n_(produz efeito na próxima mensagem)_\"\n     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)_\"\n     set_session:               \"🧠 ✓ Esforço de raciocínio definido como `{effort}` (apenas sessão — adiciona `--global` para persistir)\\n_(produz efeito na próxima mensagem)_\""]},{"file":"locales/ru.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nТекущий режим: `{mode}`\\\\n\\\\nВыберите вариант:\"\n+    choice_fast:           \"fast — Priority Processing включён\"\n+    choice_normal:         \"normal — стандартная обработка\"\n \n   footer:\n     status:                \"📎 Нижний колонтитул среды выполнения: **{state}**\\nПоля: `{fields}`\\nПлатформа: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` не поддерживается. Используйте `/reasoning <level> --global`, чтобы изменить глобальное значение по умолчанию.\"\n     reset_done:                \"🧠 ✓ Переопределение рассуждений для сеанса сброшено; возврат к глобальной конфигурации.\"\n     unknown_arg:               \"⚠️ Неизвестный аргумент: `{arg}`\\n\\n**Допустимые уровни:** none, minimal, low, medium, high, xhigh, max, ultra\\n**Отображение:** show, hide\\n**Сохранение:** добавьте `--global`, чтобы сохранить за пределами этого сеанса\"\n+    picker_title:              \"🧠 **Настройки рассуждений**\\\\n\\\\n**Усилия:** `{level}`\\\\n**Область:** {scope}\\\\n**Отображение:** {display}\\\\n\\\\nВыберите вариант:\"\n+    choice_none:               \"none — отключить рассуждения\"\n+    choice_reset:              \"reset — сбросить переопределение сессии\"\n+    choice_show:               \"показывать рассуждения в ответах\"\n+    choice_hide:               \"скрывать рассуждения в ответах\"\n     set_global:                \"🧠 ✓ Усилия рассуждений установлены на `{effort}` (сохранено в конфигурации)\\n_(вступит в силу со следующего сообщения)_\"\n     set_global_save_failed:    \"🧠 ✓ Усилия рассуждений установлены на `{effort}` (только этот сеанс — не удалось сохранить конфигурацию)\\n_(вступит в силу со следующего сообщения)_\"\n     set_session:               \"🧠 ✓ Усилия рассуждений установлены на `{effort}` (только этот сеанс — добавьте `--global`, чтобы сохранить)\\n_(вступит в силу со следующего сообщения)_\""]},{"file":"locales/tr.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nMevcut mod: `{mode}`\\\\n\\\\nBir seçenek seçin:\"\n+    choice_fast:           \"fast — Priority Processing açık\"\n+    choice_normal:         \"normal — standart işleme\"\n \n   footer:\n     status:                \"📎 Çalışma zamanı altbilgisi: **{state}**\\nAlanlar: `{fields}`\\nPlatform: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` desteklenmiyor. Genel varsayılanı değiştirmek için `/reasoning <level> --global` kullanın.\"\n     reset_done:                \"🧠 ✓ Oturumun akıl yürütme geçersiz kılması temizlendi; genel yapılandırmaya geri dönülüyor.\"\n     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\"\n+    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:\"\n+    choice_none:               \"none — akıl yürütmeyi kapat\"\n+    choice_reset:              \"reset — oturum geçersiz kılmasını temizle\"\n+    choice_show:               \"yanıtlarda akıl yürütmeyi göster\"\n+    choice_hide:               \"yanıtlarda akıl yürütmeyi gizle\"\n     set_global:                \"🧠 ✓ Akıl yürütme gücü `{effort}` olarak ayarlandı (yapılandırmaya kaydedildi)\\n_(sonraki mesajda etkili)_\"\n     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)_\"\n     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)_\""]},{"file":"locales/uk.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\nПоточний режим: `{mode}`\\\\n\\\\nОберіть варіант:\"\n+    choice_fast:           \"fast — Priority Processing увімкнено\"\n+    choice_normal:         \"normal — стандартна обробка\"\n \n   footer:\n     status:                \"📎 Нижній колонтитул середовища: **{state}**\\nПоля: `{fields}`\\nПлатформа: `{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ `/reasoning reset --global` не підтримується. Використовуйте `/reasoning <level> --global`, щоб змінити глобальне значення за замовчуванням.\"\n     reset_done:                \"🧠 ✓ Перевизначення мислення для сеансу скинуто; повернення до глобальної конфігурації.\"\n     unknown_arg:               \"⚠️ Невідомий аргумент: `{arg}`\\n\\n**Дійсні рівні:** none, minimal, low, medium, high, xhigh, max, ultra\\n**Показ:** show, hide\\n**Зберегти:** додайте `--global`, щоб зберегти поза цим сеансом\"\n+    picker_title:              \"🧠 **Налаштування мислення**\\\\n\\\\n**Зусилля:** `{level}`\\\\n**Область:** {scope}\\\\n**Показ:** {display}\\\\n\\\\nОберіть варіант:\"\n+    choice_none:               \"none — вимкнути мислення\"\n+    choice_reset:              \"reset — скинути перевизначення сесії\"\n+    choice_show:               \"показувати мислення у відповідях\"\n+    choice_hide:               \"приховувати мислення у відповідях\"\n     set_global:                \"🧠 ✓ Зусилля мислення встановлено на `{effort}` (збережено в конфігурації)\\n_(набуде чинності з наступного повідомлення)_\"\n     set_global_save_failed:    \"🧠 ✓ Зусилля мислення встановлено на `{effort}` (лише цей сеанс — не вдалося зберегти конфігурацію)\\n_(набуде чинності з наступного повідомлення)_\"\n     set_session:               \"🧠 ✓ Зусилля мислення встановлено на `{effort}` (лише цей сеанс — додайте `--global`, щоб зберегти)\\n_(набуде чинності з наступного повідомлення)_\""]},{"file":"locales/zh-hant.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **Priority Processing**\\\\n\\\\n目前模式：`{mode}`\\\\n\\\\n請選擇：\"\n+    choice_fast:           \"fast — 開啟 Priority Processing\"\n+    choice_normal:         \"normal — 標準處理\"\n \n   footer:\n     status:                \"📎 執行階段頁尾：**{state}**\\n欄位：`{fields}`\\n平台：`{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ 不支援 `/reasoning reset --global`。請使用 `/reasoning <level> --global` 變更全域預設值。\"\n     reset_done:                \"🧠 ✓ 已清除本工作階段的推理覆寫；回退至全域設定。\"\n     unknown_arg:               \"⚠️ 未知參數：`{arg}`\\n\\n**有效級別：** none, minimal, low, medium, high, xhigh, max, ultra\\n**顯示：** show, hide\\n**持久化：** 加上 `--global` 可跨工作階段儲存\"\n+    picker_title:              \"🧠 **推理設定**\\\\n\\\\n**強度：** `{level}`\\\\n**範圍：** {scope}\\\\n**顯示：** {display}\\\\n\\\\n請選擇：\"\n+    choice_none:               \"none — 停用推理\"\n+    choice_reset:              \"reset — 清除工作階段覆寫\"\n+    choice_show:               \"在回覆中顯示推理\"\n+    choice_hide:               \"在回覆中隱藏推理\"\n     set_global:                \"🧠 ✓ 推理強度已設定為 `{effort}`（已儲存到設定）\\n_（下一則訊息生效）_\"\n     set_global_save_failed:    \"🧠 ✓ 推理強度已設定為 `{effort}`（僅本工作階段 — 設定儲存失敗）\\n_（下一則訊息生效）_\"\n     set_session:               \"🧠 ✓ 推理強度已設定為 `{effort}`（僅本工作階段 — 加上 `--global` 可持久化）\\n_（下一則訊息生效）_\""]},{"file":"locales/zh.yaml","hunks":["@@ -124,6 +124,9 @@ gateway:\n     label_normal:          \"NORMAL\"\n     status_fast:           \"fast\"\n     status_normal:         \"normal\"\n+    picker_title:          \"⚡ **优先处理**\\\\n\\\\n当前模式：`{mode}`\\\\n\\\\n请选择：\"\n+    choice_fast:           \"fast — 开启优先处理\"\n+    choice_normal:         \"normal — 标准处理\"\n \n   footer:\n     status:                \"📎 运行时页脚：**{state}**\\n字段：`{fields}`\\n平台：`{platform}`\"","@@ -194,6 +197,11 @@ gateway:\n     reset_global_unsupported:  \"⚠️ 不支持 `/reasoning reset --global`。请使用 `/reasoning <level> --global` 修改全局默认值。\"\n     reset_done:                \"🧠 ✓ 已清除本会话的推理覆盖；回退到全局配置。\"\n     unknown_arg:               \"⚠️ 未知参数：`{arg}`\\n\\n**有效级别：** none, minimal, low, medium, high, xhigh, max, ultra\\n**显示：** show, hide\\n**持久化：** 添加 `--global` 以跨会话保存\"\n+    picker_title:              \"🧠 **推理设置**\\\\n\\\\n**强度：** `{level}`\\\\n**作用域：** {scope}\\\\n**显示：** {display}\\\\n\\\\n请选择：\"\n+    choice_none:               \"none — 关闭推理\"\n+    choice_reset:              \"reset — 清除会话覆盖\"\n+    choice_show:               \"在回复中显示推理\"\n+    choice_hide:               \"在回复中隐藏推理\"\n     set_global:                \"🧠 ✓ 推理强度已设置为 `{effort}`（已保存到配置）\\n_（下一条消息生效）_\"\n     set_global_save_failed:    \"🧠 ✓ 推理强度已设置为 `{effort}`（仅本会话 — 配置保存失败）\\n_（下一条消息生效）_\"\n     set_session:               \"🧠 ✓ 推理强度已设置为 `{effort}`（仅本会话 — 添加 `--global` 以持久化）\\n_（下一条消息生效）_\""]},{"file":"plugins/platforms/discord/adapter.py","hunks":["@@ -5961,6 +5961,54 @@ async def send_model_picker(\n             logger.warning(\"[%s] send_model_picker failed: %s\", self.name, e)\n             return SendResult(success=False, error=str(e))\n \n+    async def send_choice_picker(\n+        self,\n+        chat_id: str,\n+        title: str,\n+        choices: list,\n+        session_key: str,\n+        on_choice_selected,\n+        metadata: Optional[Dict[str, Any]] = None,\n+    ) -> SendResult:\n+        \"\"\"Send a flat select-menu choice picker (one selection → one value).\n+\n+        Generic single-level companion to ``send_model_picker`` used by\n+        `/reasoning`, `/fast`, and any future finite-choice command. Each\n+        choice dict: ``{\"value\": str, \"label\": str, \"is_current\": bool}``.\n+        \"\"\"\n+        if not self._client or not DISCORD_AVAILABLE:\n+            return SendResult(success=False, error=\"Not connected\")\n+\n+        try:\n+            target_id = chat_id\n+            if metadata and metadata.get(\"thread_id\"):\n+                target_id = metadata[\"thread_id\"]\n+\n+            channel = self._client.get_channel(int(target_id))\n+            if not channel:\n+                channel = await self._client.fetch_channel(int(target_id))\n+\n+            embed = discord.Embed(\n+                title=\"⚙ \" + (title.splitlines()[0] if title else \"Choose an option\"),\n+                description=\"\\n\".join(title.splitlines()[1:]) or None,\n+                color=discord.Color.blue(),\n+            )\n+\n+            view = ChoicePickerView(\n+                choices=choices,\n+                on_choice_selected=on_choice_selected,\n+                allowed_user_ids=self._allowed_user_ids,\n+                allowed_role_ids=self._allowed_role_ids,\n+            )\n+\n+            msg = await channel.send(embed=embed, view=view)\n+            view._message = msg  # store for on_timeout expiration editing\n+            return SendResult(success=True, message_id=str(msg.id))\n+\n+        except Exception as e:\n+            logger.warning(\"[%s] send_choice_picker failed: %s\", self.name, e)\n+            return SendResult(success=False, error=str(e))\n+\n     def _get_parent_channel_id(self, channel: Any) -> Optional[str]:\n         \"\"\"Return the parent channel ID for a Discord thread-like channel, if present.\"\"\"\n         parent = getattr(channel, \"parent\", None)","@@ -6850,7 +6898,7 @@ def _define_discord_view_classes() -> None:\n     lazy install sets DISCORD_AVAILABLE=True but leaves the classes\n     undefined, causing NameError on the first button interaction.\n     \"\"\"\n-    global ExecApprovalView, SlashConfirmView, UpdatePromptView, ModelPickerView, ClarifyChoiceView\n+    global ExecApprovalView, SlashConfirmView, UpdatePromptView, ModelPickerView, ClarifyChoiceView, ChoicePickerView\n \n     class ExecApprovalView(discord.ui.View):\n         \"\"\"","@@ -7547,6 +7595,97 @@ async def on_timeout(self):\n                     pass\n \n \n+    class ChoicePickerView(discord.ui.View):\n+        \"\"\"Flat select-menu view for finite-choice commands (/reasoning, /fast).\n+\n+        One dropdown, one selection, done — the generic single-level companion\n+        to ``ModelPickerView``. Auth gating mirrors ``ExecApprovalView``.\n+        Times out after 2 minutes.\n+        \"\"\"\n+\n+        def __init__(\n+            self,\n+            choices: list,\n+            on_choice_selected,\n+            allowed_user_ids: set,\n+            allowed_role_ids: Optional[set] = None,\n+        ):\n+            super().__init__(timeout=120)\n+            self.choices = list(choices)[:25]  # Discord select cap\n+            self.on_choice_selected = on_choice_selected\n+            self.allowed_user_ids = allowed_user_ids\n+            self.allowed_role_ids = allowed_role_ids or set()\n+            self.resolved = False\n+            self._message = None\n+\n+            options = []\n+            for choice in self.choices:\n+                label = str(choice.get(\"label\") or choice.get(\"value\") or \"\")\n+                options.append(\n+                    discord.SelectOption(\n+                        label=_truncate_discord_component_text(\n+                            label, _DISCORD_SELECT_FIELD_LIMIT\n+                        ),\n+                        value=str(choice.get(\"value\") or \"\"),\n+                        description=\"current\" if choice.get(\"is_current\") else None,\n+                    )\n+                )\n+            select = discord.ui.Select(\n+                placeholder=\"Choose an option...\",\n+                options=options,\n+            )\n+            select.callback = self._on_select\n+            self.add_item(select)\n+\n+        def _check_auth(self, interaction: discord.Interaction) -> bool:\n+            return _component_check_auth(\n+                interaction, self.allowed_user_ids, self.allowed_role_ids,\n+            )\n+\n+        async def _on_select(self, interaction: discord.Interaction):\n+            if not self._check_auth(interaction):\n+                await interaction.response.send_message(\n+                    \"⛔ You are not authorized to change this setting.\",\n+                    ephemeral=True,\n+                )\n+                return\n+            if self.resolved:\n+                await interaction.response.defer()\n+                return\n+            self.resolved = True\n+\n+            value = interaction.data.get(\"values\", [\"\"])[0]\n+            try:\n+                result_text = await self.on_choice_selected(\n+                    str(interaction.channel_id), value\n+                )\n+            except Exception as exc:\n+                logger.error(\"Choice picker selection failed: %s\", exc)\n+                result_text = f\"Error applying selection: {exc}\"\n+\n+            embed = discord.Embed(\n+                description=result_text,\n+                color=discord.Color.green(),\n+            )\n+            self.clear_items()\n+            self.stop()\n+            await interaction.response.edit_message(embed=embed, view=self)\n+\n+        async def on_timeout(self):\n+            if self.resolved:\n+                return\n+            msg = self._message\n+            if msg is not None:\n+                try:\n+                    embed = discord.Embed(\n+                        description=\"⏱ Selection expired — no change made.\",\n+                        color=discord.Color.greyple(),\n+                    )\n+                    self.clear_items()\n+                    await msg.edit(embed=embed, view=self)\n+                except Exception:\n+                    pass\n+\n     class ClarifyChoiceView(discord.ui.View):\n         \"\"\"Interactive button view for the clarify tool's multiple-choice prompts.\n "]},{"file":"plugins/platforms/matrix/adapter.py","hunks":["@@ -337,6 +337,21 @@ class _MatrixModelPickerPrompt:\n     bot_reaction_events: dict[str, str] = field(default_factory=dict)\n \n \n+@dataclass\n+class _MatrixChoicePickerPrompt:\n+    \"\"\"Tracks a pending Matrix reaction-based choice picker (/reasoning, /fast).\"\"\"\n+\n+    chat_id: str\n+    message_id: str\n+    session_key: str\n+    choices: dict[str, str]  # emoji -> value\n+    on_choice_selected: Any\n+    requester_user_id: str | None = None\n+    expires_at: float | None = None\n+    resolved: bool = False\n+    bot_reaction_events: dict[str, str] = field(default_factory=dict)\n+\n+\n # Matrix message size limit (4000 chars practical, spec has no hard limit\n # but clients render poorly above this).\n MAX_MESSAGE_LENGTH = 4000","@@ -386,6 +401,14 @@ class _MatrixModelPickerPrompt:\n     \"\\U0001f51f\",\n )\n \n+# Choice pickers (/reasoning, /fast) can need more than 10 slots\n+# (8 effort levels + none + reset/show/hide = 12), so extend the keycap\n+# set with lettered squares.\n+_MATRIX_CHOICE_PICKER_REACTIONS = _MATRIX_MODEL_PICKER_REACTIONS + (\n+    \"\\U0001f170\\ufe0f\",  # 🅰️\n+    \"\\U0001f171\\ufe0f\",  # 🅱️\n+)\n+\n _MATRIX_CAPABILITIES: Dict[str, str] = {\n     \"text\": \"yes\",\n     \"threads\": \"yes\",","@@ -954,6 +977,7 @@ def __init__(self, config: PlatformConfig):\n         except ValueError:\n             self._approval_timeout_seconds = 300\n         self._model_picker_prompts_by_event: Dict[str, _MatrixModelPickerPrompt] = {}\n+        self._choice_picker_prompts_by_event: Dict[str, _MatrixChoicePickerPrompt] = {}\n         allowed_users_raw = os.getenv(\"MATRIX_ALLOWED_USERS\", \"\")\n         self._allowed_user_ids: Set[str] = {\n             u.strip() for u in allowed_users_raw.split(\",\") if u.strip()","@@ -2136,6 +2160,66 @@ async def send_model_picker(\n \n         return result\n \n+    async def send_choice_picker(\n+        self,\n+        chat_id: str,\n+        title: str,\n+        choices: list,\n+        session_key: str,\n+        on_choice_selected,\n+        metadata: Optional[Dict[str, Any]] = None,\n+    ) -> SendResult:\n+        \"\"\"Send a Matrix reaction-based choice picker (/reasoning, /fast).\n+\n+        Generic single-level companion to ``send_model_picker``. Each choice\n+        dict: ``{\"value\": str, \"label\": str, \"is_current\": bool}``.\n+        \"\"\"\n+        if not self._client:\n+            return SendResult(success=False, error=\"Not connected\")\n+\n+        emoji_choices: dict[str, str] = {}\n+        lines = [title, \"\"]\n+        for i, choice in enumerate(choices):\n+            if i >= len(_MATRIX_CHOICE_PICKER_REACTIONS):\n+                break\n+            emoji = _MATRIX_CHOICE_PICKER_REACTIONS[i]\n+            value = str(choice.get(\"value\") or \"\")\n+            label = str(choice.get(\"label\") or value)\n+            if choice.get(\"is_current\"):\n+                label = f\"{label} ← current\"\n+            emoji_choices[emoji] = value\n+            lines.append(f\"{emoji} {label}\")\n+\n+        if not emoji_choices:\n+            return SendResult(success=False, error=\"No choices\")\n+\n+        lines.append(\"\")\n+        lines.append(\"React to choose.\")\n+        result = await self.send(chat_id, \"\\n\".join(lines), metadata=metadata)\n+        if not result.success or not result.message_id:\n+            return result\n+\n+        prompt = _MatrixChoicePickerPrompt(\n+            chat_id=chat_id,\n+            message_id=result.message_id,\n+            session_key=session_key,\n+            choices=emoji_choices,\n+            on_choice_selected=on_choice_selected,\n+            requester_user_id=str((metadata or {}).get(\"requester_user_id\") or \"\") or None,\n+            expires_at=time.monotonic() + max(self._approval_timeout_seconds, 0),\n+        )\n+        self._choice_picker_prompts_by_event[result.message_id] = prompt\n+\n+        for emoji in emoji_choices:\n+            try:\n+                reaction_event_id = await self._send_reaction(chat_id, result.message_id, emoji)\n+                if reaction_event_id:\n+                    prompt.bot_reaction_events[emoji] = str(reaction_event_id)\n+            except Exception as exc:\n+                logger.debug(\"Matrix: failed to add choice picker reaction %s: %s\", emoji, exc)\n+\n+        return result\n+\n     def format_message(self, content: str) -> str:\n         \"\"\"Pass-through — Matrix supports standard Markdown natively.\"\"\"\n         # Strip image markdown; media is uploaded separately.","@@ -3328,6 +3412,40 @@ async def _on_reaction(self, event: Any) -> None:\n                     )\n                 return\n \n+            choice_prompt = self._choice_picker_prompts_by_event.get(reacts_to)\n+            if choice_prompt and not choice_prompt.resolved:\n+                if room_id != choice_prompt.chat_id:\n+                    return\n+                if self._matrix_prompt_expired(choice_prompt):\n+                    self._choice_picker_prompts_by_event.pop(reacts_to, None)\n+                    return\n+                if not await self._validate_matrix_prompt_reactor(\n+                    room_id, reacts_to, sender, choice_prompt, \"choice picker\"\n+                ):\n+                    return\n+                value = choice_prompt.choices.get(key)\n+                if value is None:\n+                    await self._send_invalid_reaction_feedback(\n+                        room_id,\n+                        reacts_to,\n+                        \"That reaction is not one of the available choices.\",\n+                    )\n+                    return\n+                choice_prompt.resolved = True\n+                self._choice_picker_prompts_by_event.pop(reacts_to, None)\n+                try:\n+                    confirmation = await choice_prompt.on_choice_selected(room_id, value)\n+                    if confirmation:\n+                        await self.send(room_id, confirmation, reply_to=reacts_to)\n+                except Exception as exc:\n+                    logger.error(\"Failed to apply choice from Matrix reaction: %s\", exc)\n+                    await self.send(\n+                        room_id,\n+                        f\"Failed to apply selection: {exc}\",\n+                        reply_to=reacts_to,\n+                    )\n+                return\n+\n     def _matrix_prompt_expired(self, prompt: Any) -> bool:\n         expires_at = getattr(prompt, \"expires_at\", None)\n         return expires_at is not None and time.monotonic() > float(expires_at)"]},{"file":"plugins/platforms/telegram/adapter.py","hunks":["@@ -778,6 +778,7 @@ def __init__(self, config: PlatformConfig):\n         )\n         # Interactive model picker state per chat\n         self._model_picker_state: Dict[str, dict] = {}\n+        self._choice_picker_state: Dict[str, dict] = {}\n         # Approval button state: message_id → session_key\n         self._approval_state: Dict[int, str] = {}\n         # Slash-confirm button state: confirm_id → session_key (for /reload-mcp","@@ -5213,6 +5214,128 @@ def get_label(slug):\n             return SendResult(success=False, error=_redact_telegram_error_text(e))\n \n     _PROVIDER_PAGE_SIZE = 10\n+\n+    async def send_choice_picker(\n+        self,\n+        chat_id: str,\n+        title: str,\n+        choices: list,\n+        session_key: str,\n+        on_choice_selected,\n+        metadata: Optional[Dict[str, Any]] = None,\n+    ) -> SendResult:\n+        \"\"\"Send a flat inline-keyboard choice picker (one tap → one value).\n+\n+        Generic single-level companion to ``send_model_picker`` used by\n+        `/reasoning`, `/fast`, and any future finite-choice command. Each\n+        choice dict: ``{\"value\": str, \"label\": str, \"is_current\": bool}``.\n+        \"\"\"\n+        if not self._bot:\n+            return SendResult(success=False, error=\"Not connected\")\n+\n+        try:\n+            buttons = []\n+            for i, choice in enumerate(choices):\n+                label = str(choice.get(\"label\") or choice.get(\"value\") or \"\")\n+                if choice.get(\"is_current\"):\n+                    label = f\"✓ {label}\"\n+                buttons.append(\n+                    InlineKeyboardButton(label, callback_data=f\"cp:{i}\")\n+                )\n+            if not buttons:\n+                return SendResult(success=False, error=\"No choices\")\n+            # Two buttons per row keeps labels readable on mobile.\n+            keyboard = InlineKeyboardMarkup(\n+                [buttons[i:i + 2] for i in range(0, len(buttons), 2)]\n+            )\n+\n+            thread_id = metadata.get(\"thread_id\") if metadata else None\n+            reply_to_id = self._reply_to_message_id_for_send(None, metadata, reply_to_mode=self._reply_to_mode)\n+            msg = await self._send_message_with_thread_fallback(\n+                chat_id=normalize_telegram_chat_id(chat_id),\n+                text=self.format_message(title),\n+                parse_mode=ParseMode.MARKDOWN_V2,\n+                reply_markup=keyboard,\n+                reply_to_message_id=reply_to_id,\n+                **self._thread_kwargs_for_send(\n+                    chat_id,\n+                    thread_id,\n+                    metadata,\n+                    reply_to_message_id=reply_to_id,\n+                    reply_to_mode=self._reply_to_mode\n+                ),\n+                **self._link_preview_kwargs(),\n+            )\n+\n+            self._choice_picker_state[str(chat_id)] = {\n+                \"msg_id\": msg.message_id,\n+                \"choices\": choices,\n+                \"session_key\": session_key,\n+                \"on_choice_selected\": on_choice_selected,\n+            }\n+            return SendResult(success=True, message_id=str(msg.message_id))\n+        except Exception as e:\n+            logger.warning(\"[%s] send_choice_picker failed: %s\", self.name, _redact_telegram_error_text(e))\n+            return SendResult(success=False, error=_redact_telegram_error_text(e))\n+\n+    async def _handle_choice_picker_callback(\n+        self, query, data: str, chat_id: str\n+    ) -> None:\n+        \"\"\"Handle choice picker button taps (cp:<index>).\"\"\"\n+        state = self._choice_picker_state.get(chat_id)\n+        if not state:\n+            await query.answer(text=\"Picker expired — run the command again.\")\n+            return\n+\n+        # Same authorization gate as approval buttons: unauthorized users in a\n+        # shared group must not flip session/config state via someone else's\n+        # picker message.\n+        query_message = getattr(query, \"message\", None)\n+        query_chat = getattr(query_message, \"chat\", None)\n+        if not self._is_callback_user_authorized(\n+            str(getattr(query.from_user, \"id\", \"\")),\n+            chat_id=getattr(query_message, \"chat_id\", None),\n+            chat_type=str(getattr(query_chat, \"type\", None)) if getattr(query_chat, \"type\", None) is not None else None,\n+            thread_id=str(getattr(query_message, \"message_thread_id\", None)) if getattr(query_message, \"message_thread_id\", None) is not None else None,\n+            user_name=getattr(query.from_user, \"first_name\", None),\n+        ):\n+            await query.answer(text=\"⛔ You are not authorized to change this setting.\")\n+            return\n+\n+        try:\n+            idx = int(data[3:])\n+            choice = state[\"choices\"][idx]\n+        except (ValueError, IndexError):\n+            await query.answer(text=\"Invalid selection.\")\n+            return\n+\n+        callback = state.get(\"on_choice_selected\")\n+        if not callback:\n+            await query.answer(text=\"Picker expired.\")\n+            return\n+\n+        try:\n+            result_text = await callback(chat_id, str(choice.get(\"value\") or \"\"))\n+        except Exception as exc:\n+            logger.error(\"Choice picker selection failed: %s\", exc)\n+            result_text = f\"Error applying selection: {exc}\"\n+\n+        try:\n+            await query.edit_message_text(\n+                text=self.format_message(result_text),\n+                parse_mode=ParseMode.MARKDOWN_V2,\n+                reply_markup=None,\n+            )\n+        except Exception:\n+            try:\n+                await query.edit_message_text(\n+                    text=result_text, parse_mode=None, reply_markup=None,\n+                )\n+            except Exception:\n+                pass\n+        await query.answer()\n+        self._choice_picker_state.pop(chat_id, None)\n+\n     _MODEL_PAGE_SIZE = 8\n \n     def _build_provider_keyboard(self, providers: list, page: int = 0) -> tuple:","@@ -5709,6 +5832,13 @@ async def _handle_callback_query(\n                 await self._handle_model_picker_callback(query, data, chat_id)\n             return\n \n+        # --- Generic choice picker callbacks (/reasoning, /fast) ---\n+        if data.startswith(\"cp:\"):\n+            chat_id = str(query.message.chat_id) if query.message else None\n+            if chat_id:\n+                await self._handle_choice_picker_callback(query, data, chat_id)\n+            return\n+\n         # --- Gmail-triage callbacks (gt:verb:arg) ---\n         if data.startswith(\"gt:\"):\n             await self._handle_gmail_triage_callback("]},{"file":"tests/gateway/test_choice_picker.py","hunks":["@@ -0,0 +1,224 @@\n+\"\"\"Tests for the gateway interactive choice picker (/reasoning, /fast).\n+\n+The picker mirrors the /model picker architecture: the gateway gates on the\n+adapter *type* exposing ``send_choice_picker``, sends a flat choice list, and\n+falls back to the text status card when the platform has no picker or the\n+send fails. Selection flows through the same application path as the typed\n+command, so picker and typed arguments can never diverge.\n+\"\"\"\n+\n+import asyncio\n+from unittest.mock import AsyncMock, MagicMock\n+\n+import pytest\n+import yaml\n+\n+import gateway.run as gateway_run\n+from gateway.config import Platform\n+from gateway.platforms.base import MessageEvent, SendResult\n+from gateway.session import SessionSource\n+\n+\n+def _make_event(text=\"/reasoning\", platform=Platform.TELEGRAM, user_id=\"12345\", chat_id=\"67890\"):\n+    source = SessionSource(\n+        platform=platform,\n+        user_id=user_id,\n+        chat_id=chat_id,\n+        user_name=\"testuser\",\n+    )\n+    return MessageEvent(text=text, source=source)\n+\n+\n+class _PickerAdapter:\n+    \"\"\"Adapter whose *type* exposes ``send_choice_picker`` (the gate the\n+    handler checks via ``getattr(type(adapter), 'send_choice_picker', None)``).\"\"\"\n+\n+    def __init__(self, success=True):\n+        self.calls = []\n+        self._success = success\n+\n+    async def send_choice_picker(self, **kwargs):\n+        self.calls.append(kwargs)\n+        return SendResult(success=self._success, message_id=\"m1\")\n+\n+\n+class _NoPickerAdapter:\n+    \"\"\"Adapter with no choice-picker capability.\"\"\"\n+\n+\n+def _make_runner(adapter=None):\n+    runner = object.__new__(gateway_run.GatewayRunner)\n+    runner.adapters = {}\n+    runner._ephemeral_system_prompt = \"\"\n+    runner._prefill_messages = []\n+    runner._reasoning_config = None\n+    runner._session_reasoning_overrides = {}\n+    runner._show_reasoning = False\n+    runner._provider_routing = {}\n+    runner._fallback_model = None\n+    runner._running_agents = {}\n+    runner.hooks = MagicMock()\n+    runner.hooks.emit = AsyncMock()\n+    runner.hooks.loaded_hooks = []\n+    runner._session_db = None\n+    runner._get_or_create_gateway_honcho = lambda session_key: (None, None)\n+    runner._adapter_for_source = lambda source: adapter\n+    runner._thread_metadata_for_source = lambda source, anchor=None: {}\n+    runner._reply_anchor_for_event = lambda event: None\n+    return runner\n+\n+\n+class TestReasoningChoicePicker:\n+    @pytest.mark.asyncio\n+    async def test_bare_reasoning_sends_picker_when_adapter_supports_it(self, tmp_path, monkeypatch):\n+        monkeypatch.setattr(gateway_run, \"_hermes_home\", tmp_path)\n+        adapter = _PickerAdapter()\n+        runner = _make_runner(adapter)\n+\n+        result = await runner._handle_reasoning_command(_make_event(\"/reasoning\"))\n+\n+        assert result is None  # picker sent — adapter owns the response\n+        assert len(adapter.calls) == 1\n+        call = adapter.calls[0]\n+        values = [c[\"value\"] for c in call[\"choices\"]]\n+        # Full canonical ladder + none + subcommands, in order\n+        from hermes_constants import VALID_REASONING_EFFORTS\n+        assert values[0] == \"none\"\n+        assert values[1:1 + len(VALID_REASONING_EFFORTS)] == list(VALID_REASONING_EFFORTS)\n+        assert values[-3:] == [\"reset\", \"show\", \"hide\"]\n+\n+    @pytest.mark.asyncio\n+    async def test_bare_reasoning_falls_back_to_text_without_picker(self, tmp_path, monkeypatch):\n+        monkeypatch.setattr(gateway_run, \"_hermes_home\", tmp_path)\n+        runner = _make_runner(_NoPickerAdapter())\n+\n+        result = await runner._handle_reasoning_command(_make_event(\"/reasoning\"))\n+\n+        assert isinstance(result, str)\n+        assert \"/reasoning\" in result  # text status card\n+\n+    @pytest.mark.asyncio\n+    async def test_bare_reasoning_falls_back_to_text_when_picker_send_fails(self, tmp_path, monkeypatch):\n+        monkeypatch.setattr(gateway_run, \"_hermes_home\", tmp_path)\n+        adapter = _PickerAdapter(success=False)\n+        runner = _make_runner(adapter)\n+\n+        result = await runner._handle_reasoning_command(_make_event(\"/reasoning\"))\n+\n+        assert isinstance(result, str)\n+        assert len(adapter.calls) == 1  # attempted, then fell back\n+\n+    @pytest.mark.asyncio\n+    async def test_typed_argument_never_sends_picker(self, tmp_path, monkeypatch):\n+        monkeypatch.setattr(gateway_run, \"_hermes_home\", tmp_path)\n+        adapter = _PickerAdapter()\n+        runner = _make_runner(adapter)\n+\n+        result = await runner._handle_reasoning_command(_make_event(\"/reasoning high\"))\n+\n+        assert isinstance(result, str)\n+        assert adapter.calls == []\n+\n+    @pytest.mark.asyncio\n+    async def test_picker_selection_applies_same_as_typed(self, tmp_path, monkeypatch):\n+        \"\"\"The picker's on_choice_selected must produce the identical state\n+        change as typing the argument (single application path).\"\"\"\n+        monkeypatch.setattr(gateway_run, \"_hermes_home\", tmp_path)\n+        adapter = _PickerAdapter()\n+        runner = _make_runner(adapter)\n+        event = _make_event(\"/reasoning\")\n+        session_key = runner._session_key_for_source(event.source)\n+\n+        await runner._handle_reasoning_command(event)\n+        on_choice = adapter.calls[0][\"on_choice_selected\"]\n+\n+        reply = await on_choice(event.source.chat_id, \"ultra\")\n+\n+        assert \"ultra\" in reply\n+        override = runner._session_reasoning_overrides.get(session_key)\n+        assert override == {\"enabled\": True, \"effort\": \"ultra\"}\n+\n+    @pytest.mark.asyncio\n+    async def test_picker_selection_of_current_level_marks_is_current(self, tmp_path, monkeypatch):\n+        monkeypatch.setattr(gateway_run, \"_hermes_home\", tmp_path)\n+        (tmp_path / \"config.yaml\").write_text(\n+            yaml.safe_dump({\"agent\": {\"reasoning_effort\": \"xhigh\"}}), encoding=\"utf-8\"\n+        )\n+        adapter = _PickerAdapter()\n+        runner = _make_runner(adapter)\n+\n+        await runner._handle_reasoning_command(_make_event(\"/reasoning\"))\n+\n+        current = [c[\"value\"] for c in adapter.calls[0][\"choices\"] if c.get(\"is_current\")]\n+        assert current == [\"xhigh\"]\n+\n+    @pytest.mark.asyncio\n+    async def test_picker_show_choice_toggles_display(self, tmp_path, monkeypatch):\n+        monkeypatch.setattr(gateway_run, \"_hermes_home\", tmp_path)\n+        adapter = _PickerAdapter()\n+        runner = _make_runner(adapter)\n+        event = _make_event(\"/reasoning\")\n+\n+        await runner._handle_reasoning_command(event)\n+        on_choice = adapter.calls[0][\"on_choice_selected\"]\n+        await on_choice(event.source.chat_id, \"show\")\n+\n+        assert runner._show_reasoning is True\n+        saved = yaml.safe_load((tmp_path / \"config.yaml\").read_text(encoding=\"utf-8\"))\n+        assert saved[\"display\"][\"platforms\"][\"telegram\"][\"show_reasoning\"] is True\n+\n+\n+class TestFastChoicePicker:\n+    def _patch_fast_support(self, monkeypatch, tmp_path):\n+        monkeypatch.setattr(gateway_run, \"_hermes_home\", tmp_path)\n+        monkeypatch.setattr(gateway_run, \"_load_gateway_config\", lambda: {})\n+        monkeypatch.setattr(gateway_run, \"_resolve_gateway_model\", lambda cfg: \"gpt-5.6\")\n+        import hermes_cli.models as models_mod\n+        monkeypatch.setattr(models_mod, \"model_supports_fast_mode\", lambda m: True)\n+\n+    @pytest.mark.asyncio\n+    async def test_bare_fast_sends_picker_when_adapter_supports_it(self, tmp_path, monkeypatch):\n+        self._patch_fast_support(monkeypatch, tmp_path)\n+        adapter = _PickerAdapter()\n+        runner = _make_runner(adapter)\n+\n+        result = await runner._handle_fast_command(_make_event(\"/fast\"))\n+\n+        assert result is None\n+        values = [c[\"value\"] for c in adapter.calls[0][\"choices\"]]\n+        assert values == [\"fast\", \"normal\"]\n+\n+    @pytest.mark.asyncio\n+    async def test_fast_picker_selection_persists_service_tier(self, tmp_path, monkeypatch):\n+        self._patch_fast_support(monkeypatch, tmp_path)\n+        adapter = _PickerAdapter()\n+        runner = _make_runner(adapter)\n+        event = _make_event(\"/fast\")\n+\n+        await runner._handle_fast_command(event)\n+        on_choice = adapter.calls[0][\"on_choice_selected\"]\n+        await on_choice(event.source.chat_id, \"fast\")\n+\n+        assert runner._service_tier == \"priority\"\n+        saved = yaml.safe_load((tmp_path / \"config.yaml\").read_text(encoding=\"utf-8\"))\n+        assert saved[\"agent\"][\"service_tier\"] == \"fast\"\n+\n+    @pytest.mark.asyncio\n+    async def test_bare_fast_falls_back_to_text_without_picker(self, tmp_path, monkeypatch):\n+        self._patch_fast_support(monkeypatch, tmp_path)\n+        runner = _make_runner(_NoPickerAdapter())\n+\n+        result = await runner._handle_fast_command(_make_event(\"/fast\"))\n+\n+        assert isinstance(result, str)\n+\n+    @pytest.mark.asyncio\n+    async def test_typed_fast_argument_never_sends_picker(self, tmp_path, monkeypatch):\n+        self._patch_fast_support(monkeypatch, tmp_path)\n+        adapter = _PickerAdapter()\n+        runner = _make_runner(adapter)\n+\n+        result = await runner._handle_fast_command(_make_event(\"/fast normal\"))\n+\n+        assert isinstance(result, str)\n+        assert adapter.calls == []\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}