# NousResearch/hermes-agent · Interactive session picker and numeric selection for /resume

## Open now

- Nothing open on GitHub.

These members all concern making /resume easier to use, mainly by showing an interactive session picker when no arguments are given. Some also mention selecting a session by position number, including in the gateway, but the titles alone don't show how far any of this has been implemented.

## TL;DR

- 4 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #31709. Verify it addresses your case.
- **Recommended:** read PR #31709 (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: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: 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.

## Closed / duplicate issues

- #4988: \[Feature\]: /resume with no arguments should show an interactive session picker

## Prior & competing PRs

- **#31709: feat(cli,gateway): /resume accepts position numbers**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `cli.py`, `gateway/run.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`, `scripts/release.py`, `tests/cli/test_cli_init.py` (+2 more)
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -6172,15 +6172,16 @@ def _show_recent_sessions(self, *, reason: str = "history", limit: int = 10) ->
             else:
                 print("  Recent sessions:")
             print()
    -        print(f"  {'Title':<32} {'Preview':<40} {'Last Active':<13} {'ID'}")
    -        print(f"  {'─' * 32} {'─' * 40} {'─' * 13} {'─' * 24}")
    -        for session in sessions:
    +        print(f"  {'#':<3} {'Title':<32} {'Preview':<40} {'Last Active':<13} {'ID'}")
    +        print(f"  {'─' * 3} {'─' * 32} {'─' * 40} {'─' * 13} {'─' * 24}")
    +        for idx, session in enumerate(sessions, start=1):
                 title = session.get("title") or "—"
                 preview = (session.get("preview") or "")[:38]
                 last_active = _relative_time(session.get("last_active"))
    -            print(f"  {title:<32} {preview:<40} {last_active:<13} {session['id']}")
    +            print(f"  {idx:<3} {title:<32} {preview:<40} {last_active:<13} {session['id']}")
             print()
    -        print("  Use /resume <session id or title> to continue where you left off.")
    +        print("  Use /resume <number>, /resume <session id>, or /resume <session title> to continue.")
    +        print("  Example: /resume 2")
             print()
             return True
     
    ```

    ```diff
    @@ -6525,7 +6526,7 @@ def _handle_resume_command(self, cmd_original: str) -> None:
             target = parts[1].strip() if len(parts) > 1 else ""
     
             if not target:
    -            _cprint("  Usage: /resume <session_id_or_title>")
    +            _cprint("  Usage: /resume <number|session_id_or_title>")
                 if self._show_recent_sessions(reason="resume"):
                     return
                 _cprint("  Tip:   Use /history or `hermes sessions list` to find sessions.")
    ```

    ```diff
    @@ -6536,10 +6537,20 @@ def _handle_resume_command(self, cmd_original: str) -> None:
                 _cprint(f"  {format_session_db_unavailable()}")
                 return
     
    -        # Resolve title or ID
    -        from hermes_cli.main import _resolve_session_by_name_or_id
    -        resolved = _resolve_session_by_name_or_id(target)
    -        target_id = resolved or target
    +        # Resolve numbered selection, title, or ID
    +        if target.isdigit():
    +            sessions = self._list_recent_sessions(limit=10)
    +            index = int(target)
    +            if index < 1 or index > len(sessions):
    +                _cprint(f"  Resume index {index} is out of range.")
    +                _cprint("  Use /resume with no arguments to see available sessions.")
    +                return
    +            selected = sessions[index - 1]
    +            target_id = selected["id"]
    +        else:
    +            from hermes_cli.main import _resolve_session_by_name_or_id
    +            resolved = _resolve_session_by_name_or_id(target)
    +            target_id = resolved or target
     
             session_meta = self._session_db.get_session(target_id)
             if not session_meta:
    ```
  - `gateway/run.py`

    ```diff
    @@ -12741,7 +12741,7 @@ async def _handle_title_command(self, event: MessageEvent) -> str:
                     return t("gateway.title.current_no_title", session_id=session_id)
     
         async def _handle_resume_command(self, event: MessageEvent) -> str:
    -        """Handle /resume command — switch to a previously-named session."""
    +        """Handle /resume command — list or switch to a previous session."""
             if not self._session_db:
                 from hermes_state import format_session_db_unavailable
                 return format_session_db_unavailable(prefix=t("gateway.shared.session_db_unavailable_prefix"))
    ```

    ```diff
    @@ -12750,30 +12750,44 @@ async def _handle_resume_command(self, event: MessageEvent) -> str:
             session_key = self._session_key_for_source(source)
             name = event.get_command_args().strip()
     
    +        def _list_titled_sessions() -> list[dict]:
    +            user_source = source.platform.value if source.platform else None
    +            sessions = self._session_db.list_sessions_rich(source=user_source, limit=10)
    +            return [s for s in sessions if s.get("title")][:10]
    +
             if not name:
                 # List recent titled sessions for this user/platform
                 try:
    -                user_source = source.platform.value if source.platform else None
    -                sessions = self._session_db.list_sessions_rich(
    -                    source=user_source, limit=10
    -                )
    -                titled = [s for s in sessions if s.get("title")]
    +                titled = _list_titled_sessions()
                     if not titled:
                         return t("gateway.resume.no_named_sessions")
                     lines = [t("gateway.resume.list_header")]
    -                for s in titled[:10]:
    +                for idx, s in enumerate(titled[:10], start=1):
                         title = s["title"]
                         preview = s.get("preview", "")[:40]
                         preview_part = t("gateway.resume.list_preview_suffix", preview=preview) if preview else ""
    -                    lines.append(t("gateway.resume.list_item", title=title, preview_part=preview_part))
    -                lines.append(t("gateway.resume.list_footer"))
    +                    lines.append(t("gateway.resume.list_item_numbered", index=idx, title=title, preview_part=preview_part))
    +                lines.append(t("gateway.resume.list_footer_numbered"))
                     return "\n".join(lines)
                 except Exception as e:
                     logger.debug("Failed to list titled sessions: %s", e)
                     return t("gateway.resume.list_failed", error=e)
     
    -        # Resolve the name to a session ID.
    -        target_id = self._session_db.resolve_session_by_title(name)
    +        # Resolve a numbered choice or a title to a session ID.
    +        if name.isdigit():
    +            try:
    +                titled = _list_titled_sessions()
    +            except Exception as e:
    +                logger.debug("Failed to list titled sessions for numeric resume: %s", e)
    +                return t("gateway.resume.list_failed", error=e)
    +            index = int(name)
    +            if index < 1 or index > len(titled):
    +                return t("gateway.resume.out_of_range", index=index)
    +            target = titled[index - 1]
    +            target_id = target.get("id")
    +            name = target.get("title") or name
    +        else:
    +            target_id = self._session_db.resolve_session_by_title(name)
             if not target_id:
                 return t("gateway.resume.not_found", name=name)
             # Compression creates child continuations that hold the live transcript.
    ```
  - `locales/af.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "Geen benoemde sessies gevind nie.\nGebruik `/title My Sessie` om jou huidige sessie 'n naam te gee, en dan `/resume My Sessie` om later daarheen terug te keer."
         list_header:           "📋 **Benoemde Sessies**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nGebruik: `/resume <session name>`"
    +    list_footer_numbered:  "\nGebruik: `/resume <sessienaam>` of `/resume <nommer>` (bv. `/resume 1` vir die mees onlangse)"
         list_failed:           "Kon nie sessies lys nie: {error}"
    +    out_of_range:          "Hervat-indeks {index} is buite bereik.\nGebruik `/resume` sonder argumente om beskikbare sessies te sien."
         not_found:             "Geen sessie gevind wat by '**{name}**' pas nie.\nGebruik `/resume` sonder argumente om beskikbare sessies te sien."
         already_on:            "📌 Reeds op sessie **{name}**."
         switch_failed:         "Kon nie sessie verander nie."
    ```
  - `locales/de.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "Keine benannten Sitzungen gefunden.\nVerwenden Sie `/title Meine Sitzung`, um die aktuelle Sitzung zu benennen, dann `/resume Meine Sitzung`, um später dorthin zurückzukehren."
         list_header:           "📋 **Benannte Sitzungen**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nVerwendung: `/resume <Sitzungsname>`"
    +    list_footer_numbered:  "\nVerwendung: `/resume <Sitzungsname>` oder `/resume <Nummer>` (z. B. `/resume 1` für die zuletzt verwendete)"
         list_failed:           "Sitzungen konnten nicht aufgelistet werden: {error}"
    +    out_of_range:          "Wiederaufnahme-Index {index} liegt außerhalb des gültigen Bereichs.\nVerwenden Sie `/resume` ohne Argumente, um verfügbare Sitzungen anzuzeigen."
         not_found:             "Keine Sitzung passend zu '**{name}**' gefunden.\nVerwenden Sie `/resume` ohne Argumente, um verfügbare Sitzungen zu sehen."
         already_on:            "📌 Bereits in Sitzung **{name}**."
         switch_failed:         "Sitzungswechsel fehlgeschlagen."
    ```
  - `locales/en.yaml`

    ```diff
    @@ -237,9 +237,12 @@ gateway:
         no_named_sessions:     "No named sessions found.\nUse `/title My Session` to name your current session, then `/resume My Session` to return to it later."
         list_header:           "📋 **Named Sessions**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nUsage: `/resume <session name>`"
    +    list_footer_numbered:  "\nUsage: `/resume <session name>` or `/resume <number>` (e.g. `/resume 1` for the most recent)"
         list_failed:           "Could not list sessions: {error}"
    +    out_of_range:          "Resume index {index} is out of range.\nUse `/resume` with no arguments to see available sessions."
         not_found:             "No session found matching '**{name}**'.\nUse `/resume` with no arguments to see available sessions."
         already_on:            "📌 Already on session **{name}**."
         switch_failed:         "Failed to switch session."
    ```
  - `locales/es.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "No se encontraron sesiones con nombre.\nUsa `/title Mi sesión` para nombrar la sesión actual y luego `/resume Mi sesión` para volver a ella."
         list_header:           "📋 **Sesiones con nombre**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nUso: `/resume <nombre de sesión>`"
    +    list_footer_numbered:  "\nUso: `/resume <nombre de sesión>` o `/resume <número>` (p. ej. `/resume 1` para la más reciente)"
         list_failed:           "No se pudieron listar las sesiones: {error}"
    +    out_of_range:          "El índice de reanudación {index} está fuera de rango.\nUsa `/resume` sin argumentos para ver las sesiones disponibles."
         not_found:             "No se encontró ninguna sesión que coincida con '**{name}**'.\nUsa `/resume` sin argumentos para ver las sesiones disponibles."
         already_on:            "📌 Ya estás en la sesión **{name}**."
         switch_failed:         "No se pudo cambiar de sesión."
    ```
  - `locales/fr.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "Aucune session nommée trouvée.\nUtilisez `/title Ma session` pour nommer la session actuelle, puis `/resume Ma session` pour y revenir plus tard."
         list_header:           "📋 **Sessions nommées**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nUsage : `/resume <nom de session>`"
    +    list_footer_numbered:  "\nUtilisation : `/resume <nom de session>` ou `/resume <numéro>` (par exemple `/resume 1` pour la plus récente)"
         list_failed:           "Impossible de lister les sessions : {error}"
    +    out_of_range:          "L'index de reprise {index} est hors limites.\nUtilisez `/resume` sans arguments pour voir les sessions disponibles."
         not_found:             "Aucune session correspondant à '**{name}**' trouvée.\nUtilisez `/resume` sans argument pour voir les sessions disponibles."
         already_on:            "📌 Déjà sur la session **{name}**."
         switch_failed:         "Échec du changement de session."
    ```
  - `locales/ga.yaml`

    ```diff
    @@ -226,9 +226,12 @@ gateway:
         no_named_sessions:     "Níor aimsíodh aon seisiún ainmnithe.\nÚsáid `/title M'Ainm Seisiúin` chun do sheisiún reatha a ainmniú, ansin `/resume M'Ainm Seisiúin` chun filleadh air níos déanaí."
         list_header:           "📋 **Seisiúin Ainmnithe**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nÚsáid: `/resume <session name>`"
    +    list_footer_numbered:  "\nÚsáid: `/resume <ainm seisiúin>` nó `/resume <uimhir>` (m.sh. `/resume 1` don cheann is déanaí)"
         list_failed:           "Níorbh fhéidir seisiúin a liostáil: {error}"
    +    out_of_range:          "Tá an t-innéacs atosaithe {index} as raon.\nÚsáid `/resume` gan argóintí chun na seisiúin atá ar fáil a fheiceáil."
         not_found:             "Níor aimsíodh aon seisiún ag teacht le '**{name}**'.\nÚsáid `/resume` gan argóintí chun seisiúin atá ar fáil a fheiceáil."
         already_on:            "📌 Cheana ar an seisiún **{name}**."
         switch_failed:         "Theip ar athrú seisiúin."
    ```
  - `locales/hu.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "Nem található elnevezett munkamenet.\nHasználd a `/title Saját munkamenet` parancsot a jelenlegi munkamenet elnevezéséhez, majd a `/resume Saját munkamenet` paranccsal térhetsz vissza hozzá."
         list_header:           "📋 **Elnevezett munkamenetek**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nHasználat: `/resume <munkamenet neve>`"
    +    list_footer_numbered:  "\nHasználat: `/resume <munkamenet neve>` vagy `/resume <szám>` (pl. `/resume 1` a legutóbbihoz)"
         list_failed:           "Nem sikerült listázni a munkameneteket: {error}"
    +    out_of_range:          "A folytatási index ({index}) tartományon kívül esik.\nA `/resume` argumentumok nélküli használata megjeleníti az elérhető munkameneteket."
         not_found:             "Nem található '**{name}**' nevű munkamenet.\nArgumentumok nélkül használd a `/resume` parancsot az elérhető munkamenetek megtekintéséhez."
         already_on:            "📌 Már a **{name}** munkamenetben vagy."
         switch_failed:         "Nem sikerült munkamenetet váltani."
    ```
  - `locales/it.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "Nessuna sessione con nome trovata.\nUsa `/title My Session` per dare un nome alla sessione attuale, poi `/resume My Session` per tornare a essa in seguito."
         list_header:           "📋 **Sessioni con nome**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nUso: `/resume <session name>`"
    +    list_footer_numbered:  "\nUso: `/resume <nome sessione>` o `/resume <numero>` (es. `/resume 1` per la più recente)"
         list_failed:           "Impossibile elencare le sessioni: {error}"
    +    out_of_range:          "L'indice di ripresa {index} è fuori intervallo.\nUsa `/resume` senza argomenti per vedere le sessioni disponibili."
         not_found:             "Nessuna sessione trovata corrispondente a '**{name}**'.\nUsa `/resume` senza argomenti per vedere le sessioni disponibili."
         already_on:            "📌 Già nella sessione **{name}**."
         switch_failed:         "Cambio di sessione non riuscito."
    ```
  - `locales/ja.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "名前付きセッションが見つかりません。\n`/title セッション名` で現在のセッションに名前を付けると、後で `/resume セッション名` で戻れます。"
         list_header:           "📋 **名前付きセッション**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\n使い方: `/resume <セッション名>`"
    +    list_footer_numbered:  "\n使い方: `/resume <セッション名>` または `/resume <番号>`（例: 最新のセッションには `/resume 1`）"
         list_failed:           "セッションを一覧表示できませんでした: {error}"
    +    out_of_range:          "再開インデックス {index} は範囲外です。\n引数なしで `/resume` を実行すると、利用可能なセッションが表示されます。"
         not_found:             "'**{name}**' に一致するセッションが見つかりません。\n引数なしで `/resume` を実行すると利用可能なセッションを表示します。"
         already_on:            "📌 既にセッション **{name}** にいます。"
         switch_failed:         "セッションの切り替えに失敗しました。"
    ```
  - `locales/ko.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "이름이 지정된 세션이 없습니다.\n현재 세션에 이름을 지정하려면 `/title 내 세션`을 사용하고, 나중에 `/resume 내 세션`으로 돌아오세요."
         list_header:           "📋 **이름이 지정된 세션**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\n사용법: `/resume <session name>`"
    +    list_footer_numbered:  "\n사용법: `/resume <세션 이름>` 또는 `/resume <번호>` (예: 가장 최근 세션은 `/resume 1`)"
         list_failed:           "세션 목록을 가져올 수 없습니다: {error}"
    +    out_of_range:          "재개 인덱스 {index}이(가) 범위를 벗어났습니다.\n인자 없이 `/resume`을 실행하면 사용 가능한 세션이 표시됩니다."
         not_found:             "'**{name}**'와 일치하는 세션이 없습니다.\n사용 가능한 세션을 보려면 인수 없이 `/resume`을 사용하세요."
         already_on:            "📌 이미 **{name}** 세션에 있습니다."
         switch_failed:         "세션 전환에 실패했습니다."
    ```
  - `locales/pt.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "Não foram encontradas sessões com nome.\nUsa `/title A minha sessão` para nomear a sessão atual e depois `/resume A minha sessão` para voltar a ela."
         list_header:           "📋 **Sessões com nome**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nUso: `/resume <nome da sessão>`"
    +    list_footer_numbered:  "\nUso: `/resume <nome da sessão>` ou `/resume <número>` (ex.: `/resume 1` para a mais recente)"
         list_failed:           "Não foi possível listar as sessões: {error}"
    +    out_of_range:          "O índice de retomada {index} está fora do intervalo.\nUse `/resume` sem argumentos para ver as sessões disponíveis."
         not_found:             "Não foi encontrada nenhuma sessão correspondente a '**{name}**'.\nUsa `/resume` sem argumentos para ver as sessões disponíveis."
         already_on:            "📌 Já estás na sessão **{name}**."
         switch_failed:         "Falha ao mudar de sessão."
    ```
  - `locales/ru.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "Именованных сеансов не найдено.\nИспользуйте `/title Мой сеанс`, чтобы назвать текущий сеанс, затем `/resume Мой сеанс`, чтобы вернуться к нему позже."
         list_header:           "📋 **Именованные сеансы**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nИспользование: `/resume <название сеанса>`"
    +    list_footer_numbered:  "\nИспользование: `/resume <имя сеанса>` или `/resume <номер>` (например, `/resume 1` для самого недавнего)"
         list_failed:           "Не удалось получить список сеансов: {error}"
    +    out_of_range:          "Индекс возобновления {index} вне диапазона.\nИспользуйте `/resume` без аргументов, чтобы увидеть доступные сеансы."
         not_found:             "Сеанс, соответствующий '**{name}**', не найден.\nИспользуйте `/resume` без аргументов, чтобы увидеть доступные сеансы."
         already_on:            "📌 Уже в сеансе **{name}**."
         switch_failed:         "Не удалось переключить сеанс."
    ```
  - `locales/tr.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "Adlandırılmış oturum bulunamadı.\nMevcut oturumu adlandırmak için `/title Oturumum`, daha sonra geri dönmek için `/resume Oturumum` kullanın."
         list_header:           "📋 **Adlandırılmış Oturumlar**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nKullanım: `/resume <oturum adı>`"
    +    list_footer_numbered:  "\nKullanım: `/resume <oturum adı>` veya `/resume <numara>` (örn. en yenisi için `/resume 1`)"
         list_failed:           "Oturumlar listelenemedi: {error}"
    +    out_of_range:          "Devam endeksi {index} aralık dışında.\nKullanılabilir oturumları görmek için `/resume` komutunu argümansız çalıştırın."
         not_found:             "'**{name}**' ile eşleşen oturum bulunamadı.\nKullanılabilir oturumları görmek için argümansız `/resume` kullanın."
         already_on:            "📌 Zaten **{name}** oturumundasınız."
         switch_failed:         "Oturum değiştirilemedi."
    ```
  - `locales/uk.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "Іменованих сеансів не знайдено.\nВикористайте `/title Мій сеанс`, щоб назвати поточний сеанс, потім `/resume Мій сеанс`, щоб повернутися до нього."
         list_header:           "📋 **Іменовані сеанси**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\nВикористання: `/resume <назва сеансу>`"
    +    list_footer_numbered:  "\nВикористання: `/resume <назва сесії>` або `/resume <номер>` (наприклад, `/resume 1` для найновішої)"
         list_failed:           "Не вдалося отримати список сеансів: {error}"
    +    out_of_range:          "Індекс відновлення {index} поза межами діапазону.\nВикористовуйте `/resume` без аргументів, щоб переглянути доступні сесії."
         not_found:             "Сеанс, що відповідає '**{name}**', не знайдено.\nВикористайте `/resume` без аргументів, щоб побачити доступні сеанси."
         already_on:            "📌 Уже в сеансі **{name}**."
         switch_failed:         "Не вдалося переключити сеанс."
    ```
  - `locales/zh-hant.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "找不到已命名的工作階段。\n使用 `/title 我的工作階段` 為目前工作階段命名，然後使用 `/resume 我的工作階段` 返回。"
         list_header:           "📋 **已命名工作階段**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\n用法：`/resume <工作階段名稱>`"
    +    list_footer_numbered:  "\n用法：`/resume <會話名稱>` 或 `/resume <編號>`（例如，`/resume 1` 表示最近的會話）"
         list_failed:           "無法列出工作階段：{error}"
    +    out_of_range:          "恢復索引 {index} 超出範圍。\n請使用不帶參數的 `/resume` 查看可用會話。"
         not_found:             "找不到符合 '**{name}**' 的工作階段。\n使用不帶參數的 `/resume` 檢視可用的工作階段。"
         already_on:            "📌 已在工作階段 **{name}** 上。"
         switch_failed:         "切換工作階段失敗。"
    ```
  - `locales/zh.yaml`

    ```diff
    @@ -222,9 +222,12 @@ gateway:
         no_named_sessions:     "未找到已命名的会话。\n使用 `/title 我的会话` 为当前会话命名，然后用 `/resume 我的会话` 返回。"
         list_header:           "📋 **已命名会话**\n"
         list_item:             "• **{title}**{preview_part}"
    +    list_item_numbered:    "{index}. **{title}**{preview_part}"
         list_preview_suffix:   " — _{preview}_"
         list_footer:           "\n用法：`/resume <会话名称>`"
    +    list_footer_numbered:  "\n用法：`/resume <会话名称>` 或 `/resume <编号>`（例如，`/resume 1` 表示最近的会话）"
         list_failed:           "无法列出会话：{error}"
    +    out_of_range:          "恢复索引 {index} 超出范围。\n请使用不带参数的 `/resume` 查看可用会话。"
         not_found:             "未找到匹配 '**{name}**' 的会话。\n使用不带参数的 `/resume` 查看可用会话。"
         already_on:            "📌 已在会话 **{name}** 上。"
         switch_failed:         "切换会话失败。"
    ```
  - `scripts/release.py`

    ```diff
    @@ -1283,6 +1283,7 @@
         "[redacted:email]": "egilewski",  # PR #30432 (MEDIA path traversal fix, GHSA-jmf9-9729-7pp8)
         "[redacted:email]": "McClean-Edison",  # PR #29817 (register_auxiliary_task plugin API)
         "[redacted:email]": "SamuelZ12",  # PR #7480 (show recap after in-session resume)
    +    "[redacted:email]": "daizhonggeng",  # PR #9020 (numbered /resume selection)
     }
     
     
    ```
  - `tests/cli/test_cli_init.py`

    ```diff
    @@ -331,7 +331,8 @@ def test_resume_without_target_lists_recent_sessions(self, capsys):
     
             assert "Recent sessions" in output
             assert "Checking Running Hermes Agent" in output
    -        assert "Use /resume <session id or title> to continue" in output
    +        assert "Use /resume" in output
    +        assert "session title" in output
     
         def test_resume_updates_hermes_session_id_env_and_context(self, tmp_path):
             from gateway.session_context import _UNSET, _VAR_MAP, get_session_env
    ```
  - `tests/cli/test_cli_resume_command.py`

    ```diff
    @@ -0,0 +1,77 @@
    +from unittest.mock import MagicMock, patch
    +
    +from cli import HermesCLI
    +
    +
    +def _make_cli():
    +    cli_obj = HermesCLI.__new__(HermesCLI)
    +    cli_obj.session_id = "current_session"
    +    cli_obj._resumed = False
    +    cli_obj._pending_title = None
    +    cli_obj.conversation_history = []
    +    cli_obj.agent = None
    +    cli_obj._session_db = MagicMock()
    +    # _handle_resume_command now triggers _display_resumed_history (#31695),
    +    # which reads self.resume_display. "minimal" short-circuits the recap so
    +    # the test only exercises session-switch behavior.
    +    cli_obj.resume_display = "minimal"
    +    return cli_obj
    +
    +
    +class TestCliResumeCommand:
    +    def test_show_recent_sessions_includes_indexes_and_resume_hint(self, capsys):
    +        cli_obj = _make_cli()
    +        cli_obj._list_recent_sessions = MagicMock(return_value=[
    +            {"id": "sess_002", "title": "Coding", "preview": "build feature", "last_active": None},
    +            {"id": "sess_001", "title": "Research", "preview": "read docs", "last_active": None},
    +        ])
    +
    +        shown = cli_obj._show_recent_sessions(reason="resume")
    +        output = capsys.readouterr().out
    +
    +        assert shown is True
    +        assert "1" in output
    +        assert "2" in output
    +        assert "Coding" in output
    +        assert "Research" in output
    +        assert "/resume 2" in output
    +        assert "/resume <session title>" in output
    +
    +    def test_handle_resume_by_index_switches_to_numbered_session(self):
    +        cli_obj = _make_cli()
    +        cli_obj._list_recent_sessions = MagicMock(return_value=[
    +            {"id": "sess_002", "title": "Coding"},
    +            {"id": "sess_001", "title": "Research"},
    +        ])
    +        cli_obj._session_db.get_session.return_value = {"id": "sess_001", "title": "Research"}
    +        cli_obj._session_db.get_messages_as_conversation.return_value = [
    +            {"role": "user", "content": "hello"},
    +            {"role": "assistant", "content": "hi"},
    +        ]
    +        # resolve_resume_session_id passes the id through when no compression chain.
    +        cli_obj._session_db.resolve_resume_session_id.return_value = "sess_001"
    +
    +        with (
    +            patch("hermes_cli.main._resolve_session_by_name_or_id", return_value=None),
    +            patch("cli._cprint") as mock_cprint,
    +        ):
    +            cli_obj._handle_resume_command("/resume 2")
    +
    +        printed = " ".join(str(call) for call in mock_cprint.call_args_list)
    +        assert cli_obj.session_id == "sess_001"
    +        assert "Resumed session sess_001" in printed
    +        assert "Research" in printed
    +
    +    def test_handle_resume_by_index_out_of_range(self):
    +        cli_obj = _make_cli()
    +        cli_obj._list_recent_sessions = MagicMock(return_value=[
    +            {"id": "sess_002", "title": "Coding"},
    +        ])
    +
    +        with patch("cli._cprint") as mock_cprint:
    +            cli_obj._handle_resume_command("/resume 9")
    +
    +        printed = " ".join(str(call) for call in mock_cprint.call_args_list)
    +        assert "out of range" in printed.lower()
    +        assert "/resume" in printed
    +        assert cli_obj.session_id == "current_session"
    ```
  - `tests/gateway/test_resume_command.py`

    ```diff
    @@ -88,6 +88,9 @@ async def test_list_named_sessions_when_no_arg(self, tmp_path):
             assert "Research" in result
             assert "Coding" in result
             assert "Named Sessions" in result
    +        assert "1." in result
    +        assert "2." in result
    +        assert "/resume 1" in result
             db.close()
     
         @pytest.mark.asyncio
    ```

    ```diff
    @@ -104,6 +107,47 @@ async def test_list_shows_usage_when_no_titled(self, tmp_path):
             assert "/title" in result
             db.close()
     
    +    @pytest.mark.asyncio
    +    async def test_resume_by_index(self, tmp_path):
    +        """Numeric argument resumes the indexed titled session from the list."""
    +        from hermes_state import SessionDB
    +        db = SessionDB(db_path=tmp_path / "state.db")
    +        db.create_session("sess_001", "telegram")
    +        db.create_session("sess_002", "telegram")
    +        db.set_session_title("sess_001", "Research")
    +        db.set_session_title("sess_002", "Coding")
    +        db.create_session("current_session_001", "telegram")
    +
    +        event = _make_event(text="/resume 2")
    +        runner = _make_runner(session_db=db, current_session_id="current_session_001",
    +                              event=event)
    +        result = await runner._handle_resume_command(event)
    +
    +        assert "Resumed" in result
    +        runner.session_store.switch_session.assert_called_once()
    +        call_args = runner.session_store.switch_session.call_args
    +        assert call_args[0][1] == "sess_001"
    +        db.close()
    +
    +    @pytest.mark.asyncio
    +    async def test_resume_index_out_of_range(self, tmp_path):
    +        """Out-of-range numeric arguments show a helpful error."""
    +        from hermes_state import SessionDB
    +        db = SessionDB(db_path=tmp_path / "state.db")
    +        db.create_session("sess_001", "telegram")
    +        db.set_session_title("sess_001", "Research")
    +        db.create_session("current_session_001", "telegram")
    +
    +        event = _make_event(text="/resume 9")
    +        runner = _make_runner(session_db=db, current_session_id="current_session_001",
    +                              event=event)
    +        result = await runner._handle_resume_command(event)
    +
    +        assert "out of range" in result.lower()
    +        assert "/resume" in result
    +        runner.session_store.switch_session.assert_not_called()
    +        db.close()
    +
         @pytest.mark.asyncio
         async def test_resume_by_name(self, tmp_path):
             """Resolves a title and switches to that session."""
    ```
- **#4990: feat(cli): add /resume interactive picker and numeric selection (#4988)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#31709`
- **#8493: feat: interactive session picker for /resume and hermes resume**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#7880`

## Fix status

- Merged to main in PR #31709 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-24`.
- Subsystem: `hermes_cli`
