fix(tui): bind tab to persona cycling and ctrl+tab to preset cycling
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 1m2s
CI / helm (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 1m28s
CI / quality (pull_request) Successful in 1m23s
CI / push-validation (pull_request) Successful in 42s
CI / security (pull_request) Successful in 1m33s
CI / build (pull_request) Successful in 55s
CI / integration_tests (pull_request) Successful in 4m59s
CI / unit_tests (pull_request) Failing after 6m47s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Successful in 11m18s
CI / e2e_tests (pull_request) Failing after 16m54s
CI / status-check (pull_request) Has been cancelled
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 1m2s
CI / helm (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 1m28s
CI / quality (pull_request) Successful in 1m23s
CI / push-validation (pull_request) Successful in 42s
CI / security (pull_request) Successful in 1m33s
CI / build (pull_request) Successful in 55s
CI / integration_tests (pull_request) Successful in 4m59s
CI / unit_tests (pull_request) Failing after 6m47s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Successful in 11m18s
CI / e2e_tests (pull_request) Failing after 16m54s
CI / status-check (pull_request) Has been cancelled
Fixes #6425. Implements spec-required TUI keybindings: - tab: cycles through personas in configured cycle order - ctrl+tab: cycles through current persona's argument presets Changes: - Add PersonaState.cycle_persona() with deterministic sort ordering (cycle_order > 0 ascending first, then cycle_order == 0 by name) - Add action_cycle_persona() to TUI app - Update BINDINGS: tab -> cycle_persona, ctrl+tab -> cycle_preset - Update help panel overlay to reflect new bindings - Update BDD tests to match new binding count (4) and new scenarios - Add CHANGELOG entry under [Unreleased] -> Fixed - Add HAL9001 to CONTRIBUTORS.md
This commit is contained in:
@@ -26,6 +26,15 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
### Fixed
|
||||
|
||||
- **TUI Persona Cycling Keybinding** (#6425): Fixed TUI keyboard bindings to match the
|
||||
specification. The `tab` key now cycles through personas in the configured cycle order
|
||||
(ascending `cycle_order`, with `cycle_order == 0` personas last). The `ctrl+tab` key
|
||||
cycles through the current persona's argument presets. Previously, only `ctrl+t` was
|
||||
bound to preset cycling and persona cycling via `tab` was not implemented at all.
|
||||
Added `PersonaState.cycle_persona()` with deterministic sort ordering and
|
||||
`action_cycle_persona()` to the TUI app. Updated the help panel overlay to reflect
|
||||
the new bindings.
|
||||
|
||||
- **Trusted Automation Profile Description** (#9156): Corrected the `trusted` built-in automation profile description from `"Auto for most, human for apply and revert"` to `"Auto-exec, manual apply. Day-to-day development"` to match the specification at line 17197.
|
||||
|
||||
- **Atomic `load_from_metadata` for Autonomy Guardrails** (#7504): Fixed
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Aditya Chhabra <aditya.chhabra@cleverthis.com>
|
||||
* Brent E. Edwards <brent.edwards@cleverthis.com>
|
||||
* HAL 9000 <hal9000@cleverthis.com>
|
||||
* HAL9001 <hal9001@cleverthis.com>
|
||||
* Hamza Khyari <hamza.khyari@cleverthis.com>
|
||||
* Jeffrey Phillips Freeman <jeffrey.freeman@syncleus.com>
|
||||
* Luis Mendes <luis.p.mendes@gmail.com>
|
||||
@@ -22,6 +23,7 @@ Below are some of the specific details of various contributions.
|
||||
* HAL 9000 has contributed the benchmark workflow separation (#9040): moved the benchmark-regression job out of the default PR workflow into a dedicated scheduled workflow, reducing median PR CI turnaround time from 99-132 minutes to under 30 minutes.
|
||||
* HAL 9000 has contributed automated bug fixes, security improvements, and migration safety enhancements including the migration prompt safe-default fix (#7503).
|
||||
* HAL 9000 has contributed CONTRIBUTING.md compliance improvements to the agent-evolution-worker (#8370): replaced hardcoded milestone references with dynamic Forgejo API queries and standardised label usage to `Type/Task`.
|
||||
* HAL9001 has contributed code review and quality assurance for the TUI persona cycling keybinding fix (PR #6726 / issue #6425).
|
||||
* This project was made possible thanks to considerable donation of time, money, and resources by CleverThis, Inc.
|
||||
* HAL 9000 has contributed automated bug fixes, CLI output formatting improvements, and ongoing maintenance as part of the CleverAgents automation system.
|
||||
* HAL 9000 has contributed the file edit encoding parameter fix (PR #8258 / issue #7559).
|
||||
|
||||
@@ -388,6 +388,14 @@ def step_help_panel_contains(context, text):
|
||||
assert text in panel._text, f"Expected '{text}' in '{panel._text}'"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# action_cycle_persona
|
||||
# ---------------------------------------------------------------------------
|
||||
@when("I call action_cycle_persona on the app")
|
||||
def step_call_action_cycle_persona(context):
|
||||
context._tui_app.action_cycle_persona()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# action_cycle_preset (lines 127-129)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -125,6 +125,13 @@ def step_active_persona(context: Context, session_id: str, persona_name: str) ->
|
||||
assert persona.name == persona_name
|
||||
|
||||
|
||||
@when('I cycle persona for session "{session_id}"')
|
||||
def step_cycle_persona(context: Context, session_id: str) -> None:
|
||||
if not hasattr(context, "tui_state"):
|
||||
context.tui_state = PersonaState(registry=context.tui_registry)
|
||||
context.tui_state.cycle_persona(session_id)
|
||||
|
||||
|
||||
@when('I cycle persona preset for session "{session_id}"')
|
||||
def step_cycle_preset(context: Context, session_id: str) -> None:
|
||||
if not hasattr(context, "tui_state"):
|
||||
|
||||
@@ -47,7 +47,7 @@ Feature: TUI App Coverage
|
||||
Given a mock command router and persona state
|
||||
When I instantiate the Textual TUI app
|
||||
Then the app class should have CSS_PATH set to "cleveragents.tcss"
|
||||
And the app class should have 3 key bindings
|
||||
And the app class should have 4 key bindings
|
||||
|
||||
# --- compose method (lines 102-112) ---
|
||||
|
||||
@@ -102,6 +102,15 @@ Feature: TUI App Coverage
|
||||
And I call action_help on the app
|
||||
Then the help panel should be hidden on mount
|
||||
|
||||
# --- action_cycle_persona method ---
|
||||
|
||||
Scenario: action_cycle_persona cycles the persona and refreshes the bar
|
||||
Given a mock command router and persona state
|
||||
When I instantiate the Textual TUI app
|
||||
And I call on_mount on the app
|
||||
And I call action_cycle_persona on the app
|
||||
Then the persona bar content should be refreshed
|
||||
|
||||
# --- action_cycle_preset method (lines 127-129) ---
|
||||
|
||||
Scenario: action_cycle_preset cycles the preset and refreshes the bar
|
||||
|
||||
@@ -29,9 +29,10 @@ Feature: TUI Help Panel Overlay Coverage
|
||||
Scenario: help panel Main Screen keybindings match actual app BINDINGS
|
||||
Given a fresh help panel overlay
|
||||
When I show help for context "Main Screen"
|
||||
Then the standalone help panel text should contain "ctrl+t"
|
||||
And the standalone help panel text should not contain "ctrl+tab"
|
||||
And the standalone help panel text should not contain "tab"
|
||||
Then the standalone help panel text should contain "tab"
|
||||
And the standalone help panel text should contain "ctrl+tab"
|
||||
And the standalone help panel text should contain "Cycle to next persona"
|
||||
And the standalone help panel text should contain "Cycle to next argument preset"
|
||||
|
||||
Scenario: help panel global keybindings match actual app BINDINGS
|
||||
Given a fresh help panel overlay
|
||||
|
||||
@@ -61,3 +61,16 @@ Feature: TUI persona system
|
||||
| c |
|
||||
| d |
|
||||
Then TUI concurrent update should complete without errors
|
||||
|
||||
Scenario: Persona state cycles through personas in cycle order
|
||||
Given a temporary TUI persona registry
|
||||
And I save a TUI persona named "reviewer" with actor "local/mock-default" and cycle order 1
|
||||
And I save a TUI persona named "builder" with actor "local/mock-default" and cycle order 2
|
||||
And I save a TUI persona named "default" with actor "local/mock-default"
|
||||
When I set active persona to "reviewer" for session "cycle-s1"
|
||||
And I cycle persona for session "cycle-s1"
|
||||
Then active persona for session "cycle-s1" should be "builder"
|
||||
When I cycle persona for session "cycle-s1"
|
||||
Then active persona for session "cycle-s1" should be "default"
|
||||
When I cycle persona for session "cycle-s1"
|
||||
Then active persona for session "cycle-s1" should be "reviewer"
|
||||
|
||||
@@ -92,7 +92,8 @@ if _TEXTUAL_AVAILABLE:
|
||||
BINDINGS: ClassVar[list[tuple[str, str, str]]] = [
|
||||
("ctrl+q", "quit", "Quit"),
|
||||
("f1", "help", "Help"),
|
||||
("ctrl+t", "cycle_preset", "Cycle Preset"),
|
||||
("tab", "cycle_persona", "Cycle Persona"),
|
||||
("ctrl+tab", "cycle_preset", "Cycle Preset"),
|
||||
]
|
||||
|
||||
def __init__(
|
||||
@@ -148,6 +149,10 @@ if _TEXTUAL_AVAILABLE:
|
||||
context_name = resolve_help_context(prompt.text)
|
||||
help_panel.toggle(context_name)
|
||||
|
||||
def action_cycle_persona(self) -> None:
|
||||
self._persona_state.cycle_persona(self._session.session_id)
|
||||
self._refresh_persona_bar()
|
||||
|
||||
def action_cycle_preset(self) -> None:
|
||||
self._persona_state.cycle_preset(self._session.session_id)
|
||||
self._refresh_persona_bar()
|
||||
|
||||
@@ -8,6 +8,18 @@ from cleveragents.tui.persona.registry import PersonaRegistry
|
||||
from cleveragents.tui.persona.schema import Persona
|
||||
|
||||
|
||||
def _persona_sort_key(persona: Persona) -> tuple[int, int, str]:
|
||||
"""Return a sort key for persona cycle ordering.
|
||||
|
||||
Personas with cycle_order > 0 come first, sorted ascending.
|
||||
Personas with cycle_order == 0 (including "default") come last,
|
||||
sorted by name.
|
||||
"""
|
||||
if persona.cycle_order > 0:
|
||||
return (0, persona.cycle_order, persona.name)
|
||||
return (1, 0, persona.name)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class PersonaState:
|
||||
"""Tracks active persona per TUI session."""
|
||||
@@ -63,6 +75,29 @@ class PersonaState:
|
||||
self.preset_by_session[session_id] = next_name
|
||||
return next_name
|
||||
|
||||
def cycle_persona(self, session_id: str) -> Persona:
|
||||
"""Cycle to the next persona in the configured cycle order.
|
||||
|
||||
Personas are sorted by cycle_order (ascending, >0 first) then by name.
|
||||
The default persona is always last in the cycle. After cycling, the
|
||||
preset is reset to ``"default"``.
|
||||
"""
|
||||
all_personas = self.registry.list_personas()
|
||||
if not all_personas:
|
||||
return self.active_persona(session_id)
|
||||
sorted_personas = sorted(all_personas, key=_persona_sort_key)
|
||||
current_name = self.active_name(session_id)
|
||||
names = [p.name for p in sorted_personas]
|
||||
if current_name not in names:
|
||||
next_persona = sorted_personas[0]
|
||||
else:
|
||||
idx = names.index(current_name)
|
||||
next_persona = sorted_personas[(idx + 1) % len(sorted_personas)]
|
||||
self.active_by_session[session_id] = next_persona.name
|
||||
self.preset_by_session[session_id] = "default"
|
||||
self.registry.set_last_persona(next_persona.name)
|
||||
return next_persona
|
||||
|
||||
def effective_arguments(self, session_id: str) -> dict[str, object]:
|
||||
persona = self.active_persona(session_id)
|
||||
preset = self.current_preset(session_id)
|
||||
|
||||
@@ -32,7 +32,8 @@ _GLOBAL_ITEMS = (
|
||||
_CONTEXT_ITEMS: dict[str, tuple[tuple[str, str], ...]] = {
|
||||
"Main Screen": (
|
||||
("enter", "Submit prompt"),
|
||||
("ctrl+t", "Cycle to next argument preset"),
|
||||
("tab", "Cycle to next persona"),
|
||||
("ctrl+tab", "Cycle to next argument preset"),
|
||||
("@", "Open Reference Picker overlay"),
|
||||
("/", "Open Slash Command overlay"),
|
||||
("! / $", "Activate shell mode"),
|
||||
|
||||
Reference in New Issue
Block a user