Skip to content

feat(tui): single /model command + unified Sessions overlay#37112

Merged
austinpickett merged 8 commits into
mainfrom
bb/tui-model-session-commands
Jun 2, 2026
Merged

feat(tui): single /model command + unified Sessions overlay#37112
austinpickett merged 8 commits into
mainfrom
bb/tui-model-session-commands

Conversation

@OutThisLife

Copy link
Copy Markdown
Collaborator

Summary

Two TUI command cleanups:

1. /provider removed — /model is the only name. provider was just an alias of model in the shared registry, and in the TUI both already opened the same 2-step ModelPicker. Dropping the alias removes the duplicate from the CLI, TUI, Telegram/Slack menus, and autocomplete while keeping the nicer overlay UX.

2. /resume and /sessions merged into one Sessions overlay. Previously these were two different surfaces backed by different data — /resume browsed cold/persisted sessions (session.list, resume + delete) while /sessions switched between live in-process sessions (session.active_list, switch/close/new). /session (singular) had no command and prefix-matched the live-only switcher, so it looked empty/broken.

Now /resume, /sessions, /session, and /switch all open a single Sessions overlay:

  • "+ new" row pinned at the top (always visible — previously it was after the whole history list and scrolled off-screen), with inline prompt + model picker.
  • Live sessions with status/model/current marker — Enter switches (instant re-attach via session.activate), Ctrl+D closes.
  • Resumable history below (deduped against live) — Enter resumes from disk (session.resume), d deletes.
  • Each row dispatches the correct action by kind, so resuming can't clobber an in-flight live turn.

Collapsed the duplicate picker overlay flag into the single sessions flag and removed the now-unused sessionPicker.tsx. The 1.5s live-status poll no longer re-queries the 200-row history.

Test plan

  • npm run type-check — clean for src/ (pre-existing packages/hermes-ink errors untouched)
  • npm run lint — no errors in changed files
  • Affected vitest suites pass (createSlashHandler, activeSessionSwitcher, slashParity, orchestratorPromptSession); full TUI suite green except 2 pre-existing, unrelated text-wrapping failures (virtualHeights, cursorDriftRegression — confirmed failing on clean main)
  • Python command tests pass (tests/hermes_cli/test_commands.py, tests/gateway/test_resume_command.py, tests/gateway/test_unknown_command.py) — 169 tests
  • Manually verified in hermes --tui: /model, /session//resume//sessions open the unified overlay, pinned "+ new" row aligned and reachable

Collapse the redundant `/provider` alias so `/model` is the only name
everywhere (it already drove the same 2-step ModelPicker in the TUI).

Merge the separate `/resume` (cold history browser) and `/sessions` (live
switcher) surfaces into one Sessions overlay reached by `/resume`,
`/sessions`, `/session`, and `/switch`. It pins a "+ new" row at the top
(always visible), lists live sessions with status, and lists resumable
history below — dispatching session.activate for live rows vs resume for
cold ones, with close/delete in place. Fixes `/session` opening an empty
live-only switcher and the hidden new-session affordance.
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: bb/tui-model-session-commands vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9604 on HEAD, 9603 on base (🆕 +1)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4977 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR streamlines session/model command UX across the TUI and CLI by (1) removing the /provider alias in favor of a single /model command name, and (2) merging previously split live-session switching and persisted-session resuming into one unified “Sessions” overlay in the TUI.

Changes:

  • Remove the standalone TUI resume picker overlay (SessionPicker) and fold resumable history into the existing live session switcher overlay (pinned “+ new” row, live + resumable sections, history delete).
  • Consolidate overlay state by removing the picker flag and routing /resume, /session, and /switch to /sessions.
  • Drop the CLI /provider alias for the model command and update TUI slash handler tests accordingly.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ui-tui/src/components/sessionPicker.tsx Deletes the old resume-only picker overlay component.
ui-tui/src/components/helpHint.tsx Updates the help hint text for /resume to match the unified overlay behavior.
ui-tui/src/components/appOverlays.tsx Removes SessionPicker overlay wiring; routes resume via the sessions overlay.
ui-tui/src/components/appLayout.tsx Renames overlay callback prop from picker-specific to resume-specific.
ui-tui/src/components/activeSessionSwitcher.tsx Implements the unified Sessions overlay (new row + live + resumable history, delete/resume actions, reduced polling of history).
ui-tui/src/app/useSessionLifecycle.ts Updates resume flow to close the unified sessions overlay instead of the removed picker overlay.
ui-tui/src/app/useInputHandlers.ts Updates global escape/close behavior from overlay.picker to overlay.sessions.
ui-tui/src/app/slash/commands/session.ts Makes /sessions the unified entrypoint with aliases (/resume, /session, /switch) and adds direct resume-by-arg behavior.
ui-tui/src/app/slash/commands/core.ts Removes the legacy /resume core command implementation.
ui-tui/src/app/overlayStore.ts Removes the picker overlay flag and updates overlay blocking/reset logic.
ui-tui/src/app/interfaces.ts Removes picker from OverlayState; renames overlay prop to onResumeSelect.
ui-tui/src/tests/createSlashHandler.test.ts Updates/expands tests for unified sessions overlay and /resume argument behavior.
hermes_cli/commands.py Removes the provider alias from the model command definition.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ui-tui/src/components/activeSessionSwitcher.tsx Outdated
Comment thread ui-tui/src/app/slash/commands/session.ts
OutThisLife and others added 2 commits June 1, 2026 20:27
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Track the armed history-delete by session id instead of row index so the
  1.5s live-status poll re-indexing rows can't redirect the second `d` to a
  different session.
- Re-add the busy-session guard to immediate `/resume <id>` and `/sessions new`
  actions (browsing the bare overlay stays allowed) so resuming/switching can't
  corrupt an in-flight turn's streaming/busy state.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread ui-tui/src/components/activeSessionSwitcher.tsx
…verlay

Copilot flagged that overlay actions bypassed the busy guard. Only cold
resume actually closes the current session, so only it is guarded — both
from the slash path and now from the overlay (appActions.resumeById).
Switching between live sessions and starting a `+ new` live session keep
the current session running in the background, so they stay unguarded:
that concurrency is the orchestrator's whole purpose. Also dropped the
over-broad guard on `/sessions new` for the same reason.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comment thread ui-tui/src/components/activeSessionSwitcher.tsx
Comment thread hermes_cli/commands.py
- The 1.5s poll now re-derives the resumable list from the RAW session.list
  results (rawHistoryRef) against the current live set, so a session hidden
  while live reappears in history once it closes — instead of being lost
  until a full reload. Delete also prunes the raw ref.
- Drop the dead `/provider` entry from the desktop PICKER_OWNED_COMMANDS now
  that the alias is gone, so the desktop client no longer advertises it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Comment thread ui-tui/src/components/activeSessionSwitcher.tsx
Comment thread ui-tui/src/components/activeSessionSwitcher.tsx Outdated
…polls

- A garbled session.list response now surfaces an error and preserves the
  last good raw history, instead of silently blanking the resumable section.
- The 1.5s poll re-anchors the selection to the same row by session id
  (live or history) when the live list grows/shrinks, so the highlight no
  longer drifts to a different row mid-interaction.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Comment thread ui-tui/src/components/activeSessionSwitcher.tsx Outdated
Comment thread ui-tui/src/components/activeSessionSwitcher.tsx
- Fetch active_list and session.list via Promise.allSettled so a failing
  session.list no longer rejects the whole load: live sessions still render
  and only the resumable history degrades (with an error).
- Add unit tests for the new helpers (sessionRowKindAt row ordering,
  resumableHistory dedupe, sessionsCountLabel, relativeSessionAge).
The CI test_complete_slash_includes_provider_alias asserted the removed
`/provider` alias still autocompleted. Flip it to lock in the removal:
`/pro` no longer offers `provider`, and `/mod` still completes `model`.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

ui-tui/src/components/activeSessionSwitcher.tsx:367

  • Promise.allSettled means a rejected session.active_list request no longer hits the catch block. In that case liveRes.status !== 'fulfilled' and you currently report invalid response: session.active_list, which is misleading and drops the real RPC/network error message. Handle the rejected case explicitly (using rpcErrorMessage(liveRes.reason)) before attempting to parse the fulfilled value.
        const [liveRes, histRes] = await Promise.allSettled([
          gw.request<SessionActiveListResponse>('session.active_list', {
            current_session_id: currentSessionId
          }),
          includeHistory ? gw.request<SessionListResponse>('session.list', { limit: 200 }) : Promise.resolve(null)
        ])
        const r = liveRes.status === 'fulfilled' ? asRpcResult<SessionActiveListResponse>(liveRes.value) : null

        if (!r) {
          setErr('invalid response: session.active_list')
          setLoading(false)

@austinpickett austinpickett merged commit fabca0b into main Jun 2, 2026
23 checks passed
@austinpickett austinpickett deleted the bb/tui-model-session-commands branch June 2, 2026 02:28
changman pushed a commit to changman/hermes-agent that referenced this pull request Jun 10, 2026
…arch#37112)

* feat(tui): single /model command + unified Sessions overlay

Collapse the redundant `/provider` alias so `/model` is the only name
everywhere (it already drove the same 2-step ModelPicker in the TUI).

Merge the separate `/resume` (cold history browser) and `/sessions` (live
switcher) surfaces into one Sessions overlay reached by `/resume`,
`/sessions`, `/session`, and `/switch`. It pins a "+ new" row at the top
(always visible), lists live sessions with status, and lists resumable
history below — dispatching session.activate for live rows vs resume for
cold ones, with close/delete in place. Fixes `/session` opening an empty
live-only switcher and the hidden new-session affordance.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix(tui): address Copilot review on the Sessions overlay

- Track the armed history-delete by session id instead of row index so the
  1.5s live-status poll re-indexing rows can't redirect the second `d` to a
  different session.
- Re-add the busy-session guard to immediate `/resume <id>` and `/sessions new`
  actions (browsing the bare overlay stays allowed) so resuming/switching can't
  corrupt an in-flight turn's streaming/busy state.

* fix(tui): guard cold-resume (not live-switch/new) from the Sessions overlay

Copilot flagged that overlay actions bypassed the busy guard. Only cold
resume actually closes the current session, so only it is guarded — both
from the slash path and now from the overlay (appActions.resumeById).
Switching between live sessions and starting a `+ new` live session keep
the current session running in the background, so they stay unguarded:
that concurrency is the orchestrator's whole purpose. Also dropped the
over-broad guard on `/sessions new` for the same reason.

* fix(tui): address Copilot review (history dedup + desktop /provider)

- The 1.5s poll now re-derives the resumable list from the RAW session.list
  results (rawHistoryRef) against the current live set, so a session hidden
  while live reappears in history once it closes — instead of being lost
  until a full reload. Delete also prunes the raw ref.
- Drop the dead `/provider` entry from the desktop PICKER_OWNED_COMMANDS now
  that the alias is gone, so the desktop client no longer advertises it.

* fix(tui): surface session.list errors + keep selection stable across polls

- A garbled session.list response now surfaces an error and preserves the
  last good raw history, instead of silently blanking the resumable section.
- The 1.5s poll re-anchors the selection to the same row by session id
  (live or history) when the live list grows/shrinks, so the highlight no
  longer drifts to a different row mid-interaction.

* fix(tui): degrade session.list independently + cover overlay helpers

- Fetch active_list and session.list via Promise.allSettled so a failing
  session.list no longer rejects the whole load: live sessions still render
  and only the resumable history degrades (with an error).
- Add unit tests for the new helpers (sessionRowKindAt row ordering,
  resumableHistory dedupe, sessionsCountLabel, relativeSessionAge).

* test(tui-gateway): assert /provider alias is gone, /model remains

The CI test_complete_slash_includes_provider_alias asserted the removed
`/provider` alias still autocompleted. Flip it to lock in the removal:
`/pro` no longer offers `provider`, and `/mod` still completes `model`.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants