Files
cleveragents-core/features/tui_llm_dispatch.feature
hamza.khyari 652769c46c
CI / push-validation (pull_request) Successful in 31s
CI / helm (pull_request) Successful in 42s
CI / build (pull_request) Successful in 42s
CI / lint (pull_request) Successful in 1m6s
CI / quality (pull_request) Successful in 1m6s
CI / typecheck (pull_request) Successful in 1m12s
CI / security (pull_request) Successful in 1m25s
CI / integration_tests (pull_request) Successful in 3m17s
CI / unit_tests (pull_request) Successful in 4m37s
CI / docker (pull_request) Successful in 1m26s
CI / coverage (pull_request) Successful in 11m50s
CI / status-check (pull_request) Successful in 5s
feat(tui): wire normal text input to LLM via A2A facade
Wires the TUI normal text input path to the LLM via A2aLocalFacade,
closing the gap where typing a message produced no LLM call. All
infrastructure (facade, SessionWorkflow, ProviderRegistry) already
existed in the CLI; this PR adds the TUI wiring on top.

Key changes:
- _run_llm_dispatch(): module-level, Textual-free dispatch function
  covering all domain exception paths with user-friendly messages
- _format_worker_outcome(): testable worker result/error translator
- _create_tui_session(): DB-backed session with persona actor binding
- _build_tui_facade(): wired A2aLocalFacade + SessionWorkflow singleton
- run_worker(thread=True, exclusive=True): non-blocking, serialised
- _dispatch_gen counter: prevents cancelled worker callbacks from
  corrupting the multi-turn transcript
- SessionView.transcript: accumulated conversation history, pre-escaped
- Markup escaping: _escape() applied before transcript storage
- on_mount focus: prompt.focus() so keyboard input is received

Tests: 18 BDD scenarios + 6 Robot Framework integration tests with
FakeListLLM (no real API keys required).

ISSUES CLOSED: #11230
2026-05-22 15:12:04 +00:00

92 lines
4.2 KiB
Gherkin

Feature: TUI normal text input dispatches to LLM via A2A facade
As a user of the CleverAgents TUI
I want my normal text messages to be sent to an LLM actor
So that I can have a real conversation through the TUI interface
Background:
Given the TUI app is initialised with a mock A2A facade and session
Scenario: Normal text input dispatches to LLM and renders response
When I submit the text "hello there"
Then the facade should have received a message/send request
And the conversation should show the assistant response
Scenario: Dispatch passes active persona actor as override
When I dispatch "hello there" overriding actor with "openai/gpt-4o"
Then the facade should have received a message/send request with actor "openai/gpt-4o"
Scenario: Normal text with no facade falls back to preview mode
Given the TUI app is initialised without a facade
When I submit the text "hello there"
Then the conversation should show the preview text "hello there"
Scenario: LLM dispatch handles SessionActorNotConfiguredError gracefully
Given the facade raises SessionActorNotConfiguredError
When I submit the text "hello there"
Then the conversation should show the no-actor error message
Scenario: LLM dispatch handles generic facade error gracefully
Given the facade returns an A2A error response
When I submit the text "hello there"
Then the conversation should show an error message
Scenario: _create_tui_session returns real session_id from service
Given a mock session service that returns session_id "abc-123"
When I call _create_tui_session with that service
Then the returned session_id should be "abc-123"
Scenario: _create_tui_session falls back to default when service unavailable
Given the session service raises an exception
When I call _create_tui_session with that service
Then the returned session_id should be "default"
Scenario: LLM dispatch handles SessionNotFoundError gracefully
Given the facade raises SessionNotFoundError
When I submit the text "hello"
Then the conversation should show a session-not-found error message
Scenario: LLM dispatch handles DatabaseError gracefully
Given the facade raises DatabaseError
When I submit the text "hello"
Then the conversation should show a database error message
Scenario: Worker error is surfaced in the conversation widget
When the worker completes with error "network timeout"
Then the formatted outcome should show an error message
Scenario: Worker success result is passed through unchanged
When the worker completes with result "You: hi\n\nAssistant: hello"
Then the formatted outcome should be "You: hi\n\nAssistant: hello"
Scenario: Empty assistant response shows fallback text
Given the TUI app is initialised with a facade that returns empty response
When I submit the text "hello"
Then the conversation should show "(no response)"
Scenario: Rich markup in user input is escaped before storage in transcript
Given the TUI app is initialised with a mock A2A facade and session
When I submit the text "[bold]attack[/bold]"
Then the transcript entry should have markup escaped
Scenario: _format_worker_outcome re-raises KeyboardInterrupt
When the worker completes with a KeyboardInterrupt error
Then _format_worker_outcome should re-raise it
Scenario: _format_worker_outcome re-raises SystemExit
When the worker completes with a SystemExit error
Then _format_worker_outcome should re-raise it
Scenario: _format_worker_outcome returns None when both result and error are None
When the worker completes with neither result nor error
Then the formatted outcome should be None
Scenario: _build_tui_facade returns None when facade construction fails
When _build_tui_facade is called with a broken container
Then the returned facade should be None
Scenario: _create_tui_session binds active persona actor at session creation
Given a mock session service that returns session_id "abc-123"
And a persona state with active actor "anthropic/claude-4-sonnet"
When I call _create_tui_session with that service and persona state
Then the session should have been created with actor "anthropic/claude-4-sonnet"