docs(tui): add comprehensive TUI specification with 18 aligned ASCII mockup diagrams
Add the complete TUI (Terminal User Interface) specification section to docs/specification.md, covering architecture, layout, navigation, persona system, reference picker, slash commands, shell mode, session management, and keyboard shortcuts. The section includes 18 HTML-styled ASCII mockup diagrams using the Dracula color palette that render with actual colors in MkDocs. Key specification areas: - MainScreen layout with right-side collapsible sidebar (3 states: hidden, visible, fullscreen cycled by shift+tab) - Direct-to-chat launch with first-run actor selection overlay - Persona system bundling actor + arguments + project/plan scope, cycled with tab; argument presets cycled with ctrl+tab - @ reference picker with fuzzy search for projects/plans/resources - / slash commands mirroring CLI noun:verb patterns (session:new, plan:list, project:show, actor:switch, etc.) - ! shell mode for direct command execution - Multi-session tabs with independent personas and conversations - Escape-cascading navigation back to main screen - Plan detail view with decision tree navigation - Project detail view with resource DAG - Permission approval workflow with diff view - Complete keyboard shortcut reference table New ADR files: - ADR-044: TUI Architecture and Framework (Textual >= 1.0) - ADR-045: TUI Persona System - ADR-046: TUI Reference and Command System (CLI-aligned commands) Also adds TUI mockup CSS classes to docs/stylesheets/extra.css for Dracula-palette diagram rendering (.tui-primary, .tui-secondary, .tui-success, .tui-warning, .tui-error, .tui-dim, .tui-bold, .tui-bold-primary, .tui-bold-warning, .tui-primary-u, .tui-rainbow).
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
---
|
||||
adr_number: 44
|
||||
title: TUI Architecture and Framework
|
||||
status_history:
|
||||
- - '2026-03-10'
|
||||
- Proposed
|
||||
- Jeffrey Phillips Freeman
|
||||
- - '2026-03-10'
|
||||
- Accepted
|
||||
- Jeffrey Phillips Freeman
|
||||
tier: 2
|
||||
authors:
|
||||
- Jeffrey Phillips Freeman
|
||||
superseded_by: null
|
||||
related_adrs:
|
||||
- number: 1
|
||||
title: Layered Architecture
|
||||
relationship: TUI is a Presentation-layer component in the layered architecture
|
||||
- number: 5
|
||||
title: Technical Stack
|
||||
relationship: Adds Textual as an active dependency in the technical stack
|
||||
- number: 21
|
||||
title: CLI and Output Rendering
|
||||
relationship: TUI integrates with the Output Rendering Framework via TuiMaterializer
|
||||
- number: 26
|
||||
title: Agent Client Protocol (ACP)
|
||||
relationship: TUI communicates with the Application layer exclusively through ACP
|
||||
- number: 20
|
||||
title: Session Model
|
||||
relationship: TUI sessions map to domain Session entities
|
||||
- number: 45
|
||||
title: TUI Persona System
|
||||
relationship: Persona system is a TUI-specific concept defined in the companion ADR
|
||||
- number: 46
|
||||
title: TUI Reference and Command System
|
||||
relationship: Reference and command systems are TUI-specific input mechanisms
|
||||
acceptance:
|
||||
votes_for:
|
||||
- voter: Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>
|
||||
comment: Textual provides the reactive widget framework needed for real-time plan monitoring, multi-session management, and rich conversation rendering while enabling future Web and IDE reuse through a single UI codebase
|
||||
votes_against: []
|
||||
abstentions: []
|
||||
---
|
||||
## Context
|
||||
|
||||
CleverAgents is architected as a multi-frontend system with five Presentation-layer surfaces — CLI, TUI, Web, IDE Plugin, and REST API — all communicating through ACP (ADR-026). The CLI is fully specified and partially implemented. The TUI is the second Presentation-layer surface to be designed and represents a significant advancement in user experience: it enables real-time plan monitoring, multi-session management, interactive decision tree navigation, and rich conversation with actors — capabilities that are impractical in a stateless CLI.
|
||||
|
||||
The existing Output Rendering Framework (ADR-021) was explicitly designed to support the TUI. The `OutputSession` → `ElementHandle` → `MaterializationStrategy` pipeline is format-agnostic; producer code writes to handles without knowing whether the active strategy renders to a Rich terminal, a Textual widget tree, or a JSON serializer. The TUI needs a `TuiMaterializer` that maps handle events to Textual widget operations.
|
||||
|
||||
The system operates on multiple codebases simultaneously. Unlike single-project tools, CleverAgents manages multiple projects and plans concurrently, with plans potentially spanning multiple projects. The TUI must surface this multi-project, multi-plan reality as a first-class concern — not as an afterthought bolted onto a single-directory file browser.
|
||||
|
||||
Additionally, the TUI must support real-time event streaming from the ACP event bus to provide live visibility into plan phase transitions, tool invocations, validation results, and actor reasoning — the same events available via SSE in server mode and RxPY observables in local mode.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- The Output Rendering Framework (ADR-021) was designed with TUI integration as an explicit goal; the `TuiMaterializer` must fulfill this architectural promise without requiring changes to existing producer code
|
||||
- Real-time plan monitoring requires event-driven UI updates from the ACP event bus, not polling — the TUI must subscribe to domain events and reflect state changes immediately
|
||||
- The multi-project, multi-plan nature of CleverAgents demands a UI that shows project and plan state simultaneously alongside the conversation, not hidden behind navigation menus
|
||||
- Multiple concurrent sessions with different actors and scopes are a natural workflow for users managing several projects; the TUI must support session tabs with independent persona configurations
|
||||
- The Textual framework enables a "single UI codebase" strategy: the same widget tree can serve the standalone TUI, a Textual Web deployment, and an embedded IDE plugin — all three Presentation-layer surfaces from one implementation
|
||||
- Keyboard-driven workflows must be first-class; every operation must be achievable without a mouse, with discoverable hotkeys and consistent escape-to-return navigation
|
||||
|
||||
## Decision
|
||||
|
||||
CleverAgents adopts **Textual** (>= 1.0) as the TUI framework, with the following architectural decisions:
|
||||
|
||||
1. **Direct-to-chat launch model** — The TUI opens directly to the main chat screen (MainScreen). On first run, an actor selection overlay guides the user through initial setup. Subsequent launches restore the last active persona. There is no separate launcher/store screen.
|
||||
|
||||
2. **Right-side collapsible sidebar with three states** — The sidebar is docked to the right edge (not left, to keep the conversation — the primary interaction surface — aligned with the terminal's natural reading position). It cycles through three states via `shift+tab`: **hidden** → **visible** (partial, ~32 chars wide) → **fullscreen** (covers the conversation and takes input focus for plan/project/persona management). The fullscreen state is a dedicated management interface, not merely a wider sidebar.
|
||||
|
||||
3. **Multi-session tab bar** — Multiple concurrent sessions are supported, each with an independent persona, conversation history, and ACP session binding. Session tabs appear at the top of the screen and are navigable with `ctrl+[` / `ctrl+]`. Tabs auto-show when two or more sessions exist.
|
||||
|
||||
4. **TuiMaterializer integration** — A new `TuiMaterializer` implementation of `MaterializationStrategy` maps `ElementHandle` events to Textual widget operations. Each handle type maps to a specific Textual widget (see Design section). This enables all existing CLI command producers to render in the TUI without modification.
|
||||
|
||||
5. **ACP event subscription** — The TUI subscribes to ACP events (local mode: RxPY observables; server mode: SSE) for real-time updates to the sidebar plan/project panels, conversation stream, and notification system.
|
||||
|
||||
6. **Escape-cascading navigation** — Pressing `escape` always moves toward the main screen: closing modals, dismissing overlays, exiting fullscreen sidebar, and eventually returning to the prompt. Multiple escape presses from any state eventually reach the main chat screen.
|
||||
|
||||
7. **Window-specific hotkey contexts** — Each screen and focused widget defines its own hotkey bindings. The help panel (`F1`) dynamically reflects the current context's available hotkeys.
|
||||
|
||||
## Design
|
||||
|
||||
### Application Class and Screen Architecture
|
||||
|
||||
The root `CleverAgentsApp(App)` manages screen modes and global state:
|
||||
|
||||
```
|
||||
CleverAgentsApp(App)
|
||||
│
|
||||
├── CSS_PATH = "cleveragents.tcss" (global styles)
|
||||
│
|
||||
├── Screens:
|
||||
│ ├── MainScreen (per-session, dynamically created modes)
|
||||
│ ├── SidebarFullScreen (pushed when sidebar enters fullscreen)
|
||||
│ ├── PlanDetailModal (pushed from sidebar or conversation)
|
||||
│ ├── ProjectDetailModal (pushed from sidebar)
|
||||
│ ├── PersonaEditorModal (pushed from fullscreen sidebar)
|
||||
│ ├── SettingsScreen (pushed via F2 / ctrl+,)
|
||||
│ ├── SessionsScreen (pushed via ctrl+s)
|
||||
│ └── PermissionsScreen (pushed for tool permission requests)
|
||||
│
|
||||
├── Global State:
|
||||
│ ├── SessionTracker (active sessions, current session)
|
||||
│ ├── PersonaRegistry (loaded from ~/.config/cleveragents/personas/)
|
||||
│ ├── Settings (TUI configuration, reactive)
|
||||
│ └── ACPClient (local facade or HTTP transport)
|
||||
│
|
||||
└── Theme:
|
||||
└── Default: "dracula" (17+ Textual built-in themes supported)
|
||||
```
|
||||
|
||||
### MainScreen Layout
|
||||
|
||||
The MainScreen is the primary interface. It uses a horizontal layout with the conversation taking available space and the sidebar docked to the right:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┬──────────────────────┐
|
||||
│ Throbber (height: 1, full width, visible when busy) │ │
|
||||
│ SessionTabs (height: auto, visible when ≥2 sessions)│ │
|
||||
├─────────────────────────────────────────────────────┤ SideBar │
|
||||
│ │ (dock: right) │
|
||||
│ Conversation │ (width: 32-40) │
|
||||
│ (flex: 1fr) │ (max-width: 45%) │
|
||||
│ │ ┌────────────────┐ │
|
||||
│ ┌─ ContentsGrid ─────────────────────────────────┐ │ │ PlansPanel │ │
|
||||
│ │ Cursor │ Contents stream │ │ │ (collapsible) │ │
|
||||
│ │ (1ch) │ - Welcome / UserInput / ActorResponse│ │ │ │ │
|
||||
│ │ │ - ToolCall / PlanProgress / DiffView │ │ ├────────────────┤ │
|
||||
│ │ │ - TerminalEmbed / Note / Warning │ │ │ ProjectsPanel │ │
|
||||
│ └────────┴───────────────────────────────────────┘ │ │ (collapsible) │ │
|
||||
│ │ │ │ │
|
||||
│ Flash (notification bar, height: 1) │ └────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─ Prompt ───────────────────────────────────────┐ │ │
|
||||
│ │ ReferencePickerOverlay (overlay, triggered by @)│ │ │
|
||||
│ │ SlashCommandOverlay (overlay, triggered by /) │ │ │
|
||||
│ │ ┌─ PromptContainer ─────────────────────────┐ │ │ │
|
||||
│ │ │ [❯] PromptTextArea │ │ │ │
|
||||
│ │ └───────────────────────────────────────────┘ │ │ │
|
||||
│ │ PersonaBar: name │ actor │ preset │ cost │ │ │
|
||||
│ └─────────────────────────────────────────────────┘ │ │
|
||||
├─────────────────────────────────────────────────────┴──────────────────────┤
|
||||
│ Footer: F1 Help │ shift+tab Sidebar │ tab Persona │ ctrl+q Quit │
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Sidebar Three-State Behavior
|
||||
|
||||
The sidebar cycles through three states via `shift+tab`:
|
||||
|
||||
| State | Layout | Input Focus | Content |
|
||||
|-------|--------|-------------|---------|
|
||||
| **Hidden** | Sidebar has `display: none`; conversation takes full width | Prompt retains focus | No sidebar content visible |
|
||||
| **Visible** | Sidebar docked right, 32-40 chars wide (configurable) | Prompt retains focus; `ctrl+b` focuses sidebar | Plans and Projects panels in collapsible containers; read-only status display |
|
||||
| **Fullscreen** | Sidebar covers entire screen; MainScreen conversation hidden | Sidebar takes input focus; typing goes to sidebar search/navigation, not the prompt | Extended plan/project details, selection mode for persona building, persona cycle list management |
|
||||
|
||||
State transitions:
|
||||
```
|
||||
Hidden ──shift+tab──► Visible ──shift+tab──► Fullscreen
|
||||
▲ │
|
||||
└──────────────── escape (×1-2) ───────────────┘
|
||||
```
|
||||
|
||||
In fullscreen mode, `escape` returns to visible; another `escape` returns to hidden. This cascading escape behavior is consistent across all TUI states.
|
||||
|
||||
### TuiMaterializer — Output Rendering Framework Integration
|
||||
|
||||
The `TuiMaterializer` bridges the existing `OutputSession` / `ElementHandle` pipeline (ADR-021) to Textual widgets. Producer code is completely unaware of the TUI — the same command logic that renders `rich` tables and panels in the CLI renders interactive Textual widgets in the TUI.
|
||||
|
||||
| ElementHandle Type | Textual Widget Mapping | Behavior |
|
||||
|--------------------|------------------------|----------|
|
||||
| `PanelHandle` | `Static` container with key-value `Label` pairs inside a `Collapsible` | `set_entry()` → update/add label; `remove_entry()` → remove label; border styled by `border_style` parameter |
|
||||
| `TableHandle` | `DataTable` widget | `add_row()` → `add_row()` on DataTable; `set_sort_key()` → re-sort; `set_summary()` → footer row |
|
||||
| `TreeHandle` | `Tree` widget | `add_child()` → `add_leaf()` or `add()` on tree node; `set_node_style()` → update node classes |
|
||||
| `ProgressHandle` | `ProgressBar` (determinate) or custom `Throbber` (indeterminate) | `set_progress()` → update bar; `set_step_status()` → update step indicator; `increment()` → advance bar |
|
||||
| `StatusHandle` | `Label` with semantic CSS class (`-info`, `-warning`, `-error`, `-success`) | `set_message()` → update label text; `set_level()` → swap CSS class |
|
||||
| `CodeHandle` | Read-only `TextArea` with syntax highlighting via tree-sitter | `set_content()` → replace text; `set_language()` → change grammar; `set_highlight_lines()` → update gutter markers |
|
||||
| `DiffHandle` | Custom `DiffView` widget supporting unified and side-by-side modes | `add_hunk()` → append hunk to diff display; `set_stats()` → update stats header |
|
||||
| `SeparatorHandle` | `Rule` widget | Auto-closed on creation; renders as horizontal rule |
|
||||
| `ActionHintHandle` | `Static` with muted text and command references | Auto-closed; renders suggested next commands |
|
||||
|
||||
The `TuiMaterializer` observes `ElementEvent` notifications from the `OutputSession`:
|
||||
|
||||
- `ElementCreated` → instantiate the corresponding Textual widget and mount it in the conversation's `Contents` container
|
||||
- `ElementUpdated` → call the appropriate update method on the mounted widget (reactive binding ensures screen refresh)
|
||||
- `ElementClosed` → mark the widget as finalized (disable loading indicators, settle animations)
|
||||
- `SessionEnd` → flush any pending updates
|
||||
|
||||
### ACP Event Subscription Architecture
|
||||
|
||||
The TUI subscribes to ACP events to provide real-time visibility into system activity:
|
||||
|
||||
```
|
||||
ACPClient (local or server)
|
||||
│
|
||||
├── events.subscribe(plan_id=*, event_type=*)
|
||||
│ │
|
||||
│ ├──► plan.phase_changed ──► SideBar.PlansPanel: update phase indicator
|
||||
│ ├──► plan.state_changed ──► SideBar.PlansPanel: update state badge
|
||||
│ ├──► plan.decision_made ──► SideBar.PlansPanel: update depth counter
|
||||
│ │ PlanDetailModal: add node to decision tree
|
||||
│ ├──► tool.invoked ──► Conversation: mount ToolCall widget (pending)
|
||||
│ ├──► tool.completed ──► Conversation: update ToolCall widget (done/fail)
|
||||
│ ├──► validation.completed ──► Conversation: append validation result
|
||||
│ ├──► plan.apply_completed ──► SideBar: update terminal state
|
||||
│ │ Flash: show completion notification
|
||||
│ ├──► plan.error ──► Flash: show error notification
|
||||
│ │ SideBar: mark plan as errored
|
||||
│ └──► session.message ──► Conversation: stream ActorResponse content
|
||||
│
|
||||
└── Local mode: RxPY Observable subscription
|
||||
Server mode: SSE connection to /api/v1/events/stream
|
||||
```
|
||||
|
||||
### Conversation Stream Architecture
|
||||
|
||||
The conversation displays a chronological stream of message blocks, each typed for its content:
|
||||
|
||||
| Block Type | Visual Treatment | Source |
|
||||
|------------|-----------------|--------|
|
||||
| `Welcome` | ASCII art + first-run instructions in `$text-success` | App startup (first message only) |
|
||||
| `UserInput` | Left border `$secondary`, 15% background tint, Markdown rendered | User prompt submission |
|
||||
| `ActorResponse` | Streaming Markdown with syntax-highlighted code fences | `session.message` events |
|
||||
| `ActorThought` | `$primary-muted` 20% background, max-height 10 lines (expandable), italic | Actor thinking/reasoning (if exposed) |
|
||||
| `ToolCall` | Expandable: header with tool icon + status pill, collapsible content with DiffView/text/Markdown | `tool.invoked` / `tool.completed` events |
|
||||
| `PlanProgress` | Grid layout with status icons per step | Plan phase changes |
|
||||
| `DiffView` | Unified or side-by-side diff with syntax highlighting, line numbers | Tool results containing diffs |
|
||||
| `TerminalEmbed` | Bordered terminal widget with `$primary 50%` border, green (success) or red (error) tint | Shell commands or tool terminal output |
|
||||
| `ShellResult` | Left border `$primary`, 4% foreground background | User shell command (!) output |
|
||||
| `Note` | Semantic styling: info (`$primary`), warning (`$warning`), error (`$error`) | System notifications |
|
||||
|
||||
The conversation uses a 2-column grid: a 1-character-wide cursor column (left) with a block cursor navigable via `alt+up`/`alt+down`, and the content stream (right). The block cursor provides keyboard-driven message navigation — pressing `enter` or `space` on a block expands/collapses it.
|
||||
|
||||
### Prompt Architecture
|
||||
|
||||
The prompt area is docked to the bottom of the conversation and comprises:
|
||||
|
||||
1. **Overlay zone** — `ReferencePickerOverlay` and `SlashCommandOverlay` render as floating panels above the prompt, triggered by `@` and `/` respectively
|
||||
2. **PromptContainer** — houses the `PromptTextArea` (syntax-highlighted, multi-line capable) with a mode-dependent prompt symbol:
|
||||
- `❯` — normal prompt mode (Markdown input)
|
||||
- `$` — shell mode (activated by `!` at position 0)
|
||||
- `☰` — multi-line mode (activated when input contains newlines)
|
||||
3. **QuestionWidget** — replaces the prompt during actor permission requests (allow/reject choices)
|
||||
4. **PersonaBar** — always visible below the prompt, showing: persona name, actor name/model, current argument preset name (from ctrl+tab cycling), scope indicator (number of scoped projects/plans), and cumulative session cost
|
||||
|
||||
### Theme and Styling Architecture
|
||||
|
||||
The TUI uses Textual's CSS (TCSS) system with semantic color tokens:
|
||||
|
||||
| Token | Usage |
|
||||
|-------|-------|
|
||||
| `$primary` | Active elements, sidebar borders, plan status indicators |
|
||||
| `$secondary` | User input highlights, prompt accent, info bar |
|
||||
| `$success` | Completed states, passed validations, welcome text |
|
||||
| `$error` | Failed states, error notifications, dangerous commands |
|
||||
| `$warning` | Caution states, cost warnings, non-rollbackable alerts |
|
||||
| `$text`, `$text-muted` | Primary and secondary text |
|
||||
| `$text-primary`, `$text-secondary` | Accent-colored text |
|
||||
| `$panel` | Panel/container backgrounds |
|
||||
|
||||
Default theme is **Dracula**. All 17+ Textual built-in themes are supported and switchable via `/theme <name>` command or settings. The TCSS file hierarchy is:
|
||||
|
||||
```
|
||||
src/cleveragents/tui/
|
||||
├── cleveragents.tcss (global app styles)
|
||||
├── screens/
|
||||
│ ├── main.tcss (MainScreen layout and conversation)
|
||||
│ ├── sidebar_full.tcss (SidebarFullScreen)
|
||||
│ ├── plan_detail.tcss (PlanDetailModal)
|
||||
│ ├── project_detail.tcss (ProjectDetailModal)
|
||||
│ ├── persona_editor.tcss (PersonaEditorModal)
|
||||
│ ├── settings.tcss (SettingsScreen)
|
||||
│ ├── sessions.tcss (SessionsScreen)
|
||||
│ └── permissions.tcss (PermissionsScreen)
|
||||
└── widgets/
|
||||
├── conversation.tcss (conversation stream widgets)
|
||||
├── prompt.tcss (prompt area styling)
|
||||
├── sidebar.tcss (sidebar panels)
|
||||
└── overlays.tcss (reference picker, slash commands)
|
||||
```
|
||||
|
||||
### Throbber Design
|
||||
|
||||
A rainbow gradient animated bar spans the full width of the screen at the top, visible only when the actor is processing. The gradient cycles through:
|
||||
|
||||
```
|
||||
#881177 → #aa3355 → #cc6666 → #ee9944 → #eedd00 → #99dd55 →
|
||||
#44dd88 → #22ccbb → #00bbcc → #0099cc → #3366bb → #663399
|
||||
```
|
||||
|
||||
Animation rate: 15fps. Height: 1 row. When idle, the throbber row collapses to zero height.
|
||||
|
||||
### First-Run Experience
|
||||
|
||||
On first launch (no personas configured), the TUI opens to the MainScreen with a centered modal overlay for actor selection:
|
||||
|
||||
1. The overlay lists all registered actors (discovered from the Actor Registry via ACP)
|
||||
2. The user selects an actor with arrow keys + enter, or types `/` to search
|
||||
3. Selection creates a default persona named `"default"` with the chosen actor and no scoped projects/plans
|
||||
4. The overlay dismisses and the user can begin chatting
|
||||
|
||||
On subsequent launches, the TUI restores the last active persona and session.
|
||||
|
||||
### Multi-Session Management
|
||||
|
||||
Each session tab represents an independent domain `Session` (ADR-020):
|
||||
|
||||
- Sessions have independent conversation histories
|
||||
- Each session binds to a persona (which can differ per session)
|
||||
- Session tabs show state indicators: `⌛` (actor working), `❯` (awaiting user input), plain (idle)
|
||||
- `ctrl+n` creates a new session tab with the current persona
|
||||
- `ctrl+w` closes the current session tab (with confirmation if unsaved)
|
||||
- Session state is persisted; `ctrl+r` resumes a previous session from the sessions screen
|
||||
|
||||
## Constraints
|
||||
|
||||
- The TUI must communicate with the Application layer exclusively through ACP — no direct imports from Domain or Infrastructure layers (enforced by import-linter architecture tests)
|
||||
- All TUI functionality must be accessible via keyboard alone; mouse support is supplementary
|
||||
- The TUI must not block the main thread during ACP operations; all ACP calls are async
|
||||
- The TUI must handle both local mode (in-process ACP) and server mode (HTTPS ACP) transparently — the transport is selected at startup based on configuration
|
||||
- The `TuiMaterializer` must not require changes to existing `OutputSession` or `ElementHandle` interfaces — it is a drop-in `MaterializationStrategy` implementation
|
||||
- Persona definitions are stored locally in user configuration space (`~/.config/cleveragents/personas/`) and are not part of the domain model — they do not round-trip through ACP or persist in the database
|
||||
- The escape key must always navigate toward the main screen from any state; no modal or screen may trap the user without an escape path
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- The Output Rendering Framework's `TuiMaterializer` validates the framework's design promise: existing CLI command producers render in the TUI with zero code changes
|
||||
- Real-time ACP event subscription provides immediate visibility into plan progress, eliminating the need to poll `plan status` from the CLI
|
||||
- The multi-session tab model enables parallel workflows — a user can monitor a long-running plan in one tab while chatting about a different project in another
|
||||
- The right-side sidebar keeps the conversation (primary interaction surface) left-aligned, matching natural reading direction and terminal cursor behavior
|
||||
- Textual's "single UI codebase" strategy means the TUI implementation also serves as the Web frontend (via Textual Web) and IDE plugin (embedded Textual) with minimal additional work
|
||||
- The three-state sidebar provides progressive disclosure: hidden for focused chat, visible for at-a-glance status, fullscreen for deep management operations
|
||||
|
||||
### Negative
|
||||
|
||||
- Adding Textual as a dependency increases the installation footprint; CLI-only users who never launch the TUI still carry the dependency (mitigated by making it an optional extra: `pip install cleveragents[tui]`)
|
||||
- The `TuiMaterializer` must handle all `ElementHandle` types from day one — partial implementation would cause silent failures when CLI commands run in TUI context
|
||||
- Real-time event subscription creates a persistent connection (RxPY observable or SSE stream) that must be managed across session tab switches and app lifecycle
|
||||
|
||||
### Risks
|
||||
|
||||
- Textual's API is still evolving; pinning to >= 1.0 reduces but does not eliminate the risk of breaking changes in minor versions
|
||||
- Complex conversation streams (many tool calls, large diffs) may cause performance issues; the conversation should implement pruning (collapse old messages) when line count exceeds configurable thresholds
|
||||
- The three-state sidebar transition must be smooth and not cause layout thrashing — CSS transitions and careful height/width management are required
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
**Urwid** — A mature terminal UI library with lower-level widget primitives. Rejected because it lacks Textual's reactive data binding, CSS-based styling, and the Textual Web pathway for future Web/IDE deployment. Would require significantly more custom widget development.
|
||||
|
||||
**Prompt Toolkit** — Excellent for input handling but not designed for full-screen multi-widget layouts. Would require combining with another library (e.g., Rich Live) for the conversation stream, resulting in a fragmented architecture. Rejected in favor of Textual's integrated approach.
|
||||
|
||||
**Blessed / Blessed-Contrib (Python port)** — Node.js-inspired terminal UI. Less Pythonic, smaller community, no web deployment path. Rejected.
|
||||
|
||||
**Custom Rich Live application** — Build the TUI using Rich's Live display and manual layout management. Feasible for simple displays but does not scale to the complexity needed (multi-screen, overlays, focus management, event handling). Rejected as insufficiently structured.
|
||||
|
||||
## Compliance
|
||||
|
||||
- **Architecture tests**: Import-linter rules verify that `src/cleveragents/tui/` imports only from `src/cleveragents/cli/output/` (for `TuiMaterializer` integration) and ACP client interfaces — never from Domain or Infrastructure layers
|
||||
- **Widget tests**: Each custom Textual widget has unit tests using Textual's `pilot` testing framework, verifying rendering, event handling, and reactive state updates
|
||||
- **Integration tests**: End-to-end tests launch the TUI in headless mode (Textual's `run_test()` context), simulate user input via pilot, and verify ACP operations are invoked correctly
|
||||
- **Hotkey tests**: Every documented hotkey binding is covered by a pilot test that simulates the keypress and verifies the expected action
|
||||
- **Materialization tests**: The `TuiMaterializer` is tested against the same `OutputSession` test fixtures used for `RichMaterializer`, `PlainMaterializer`, etc., verifying that all element handle types produce the expected Textual widgets
|
||||
- **Accessibility tests**: Automated checks verify that all interactive elements are keyboard-reachable and that focus order follows logical navigation paths
|
||||
- **Performance tests**: Conversation stream benchmarks verify that rendering remains responsive with 500+ message blocks (the pruning threshold)
|
||||
- **Theme tests**: Visual regression tests capture screenshots of key screens in multiple themes and compare against baselines
|
||||
@@ -0,0 +1,316 @@
|
||||
---
|
||||
adr_number: 45
|
||||
title: TUI Persona System
|
||||
status_history:
|
||||
- - '2026-03-10'
|
||||
- Proposed
|
||||
- Jeffrey Phillips Freeman
|
||||
- - '2026-03-10'
|
||||
- Accepted
|
||||
- Jeffrey Phillips Freeman
|
||||
tier: 4
|
||||
authors:
|
||||
- Jeffrey Phillips Freeman
|
||||
superseded_by: null
|
||||
related_adrs:
|
||||
- number: 10
|
||||
title: Actor and Agent Architecture
|
||||
relationship: Personas reference actors defined by the actor architecture
|
||||
- number: 17
|
||||
title: Automation Profiles
|
||||
relationship: Persona argument presets may override automation profile selection
|
||||
- number: 20
|
||||
title: Session Model
|
||||
relationship: Each TUI session binds to a persona which configures the session's actor and scope
|
||||
- number: 44
|
||||
title: TUI Architecture and Framework
|
||||
relationship: Personas are a TUI-specific concept within the TUI architecture
|
||||
- number: 9
|
||||
title: Project Model
|
||||
relationship: Personas scope to projects defined by the project model
|
||||
- number: 6
|
||||
title: Plan Lifecycle
|
||||
relationship: Personas scope to plans that follow the plan lifecycle
|
||||
acceptance:
|
||||
votes_for:
|
||||
- voter: Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>
|
||||
comment: The persona abstraction elegantly bundles the TUI-specific combination of actor selection, argument configuration, project/plan scoping, and argument presets into a single switchable unit — solving the multi-project workflow ergonomics problem without polluting the domain model
|
||||
votes_against: []
|
||||
abstentions: []
|
||||
---
|
||||
## Context
|
||||
|
||||
In the CLI, each invocation specifies its configuration explicitly: `agents plan use --actor anthropic/claude-4 --project local/api-service --automation-profile trusted ...`. This is appropriate for stateless command execution but creates friction in the TUI, where users conduct long-running interactive sessions and frequently switch between different actor configurations, project scopes, and reasoning modes.
|
||||
|
||||
Consider a developer managing three projects simultaneously. In the CLI, they would run separate commands with different flags for each project. In the TUI, they need a way to quickly switch their entire working context — the actor, its configuration arguments, and the set of projects and plans the actor should focus on — without re-typing configuration every time.
|
||||
|
||||
Additionally, the CleverAgents system supports actors with configurable arguments (e.g., `thinking_effort`, `temperature`, `max_tokens`). Power users frequently want to toggle between reasoning modes (e.g., "quick answer" vs. "deep analysis") within the same session. This requires a mechanism to define named argument overlays that can be cycled through rapidly.
|
||||
|
||||
None of these concerns belong in the domain model: they are purely Presentation-layer ergonomics for the TUI. The domain layer knows about actors, sessions, projects, and plans — but not about the TUI-specific bundling of these into a switchable unit.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Users working with multiple projects need to switch their entire working context (actor, arguments, project scope, plan scope) as a single atomic operation, not as individual flag changes
|
||||
- Argument presets (e.g., cycling through thinking effort levels) are a common workflow pattern that should be achievable with a single keypress, not by editing configuration
|
||||
- The persona concept must remain a TUI-only abstraction — it must not leak into the domain model, ACP protocol, or database schema
|
||||
- Default behavior must be sensible without any persona configuration: first-run users should get a working persona automatically based on their actor selection
|
||||
- The persona system must integrate cleanly with the existing actor argument system — presets overlay arguments, they do not replace them
|
||||
- Personas must be portable (exportable/importable) for team sharing and machine migration
|
||||
|
||||
## Decision
|
||||
|
||||
CleverAgents introduces the **Persona** concept as a TUI-only abstraction that bundles:
|
||||
|
||||
1. **Actor reference** — a namespaced actor name (e.g., `anthropic/claude-4-sonnet`)
|
||||
2. **Base arguments** — the default arguments passed to the actor on every invocation
|
||||
3. **Scoped projects** — a list of project references that are always included in the session's context
|
||||
4. **Scoped plans** — a list of plan references that are always included in the session's context
|
||||
5. **Argument presets** — named sets of argument overrides that can be cycled with `ctrl+tab`
|
||||
6. **Display metadata** — a short name, optional accent color, and description
|
||||
|
||||
Personas are:
|
||||
|
||||
- **Stored locally** in `~/.config/cleveragents/personas/` as individual YAML files
|
||||
- **Cyclable** via `tab` in the main chat screen, cycling through a user-configured ordered list
|
||||
- **Session-bound** — each TUI session has a current persona; changing the persona mid-session updates the actor and scope for subsequent prompts (previous conversation history is retained)
|
||||
- **Auto-generated** on first run from the actor selection, with thinking-level presets auto-detected from the actor's argument schema
|
||||
|
||||
## Design
|
||||
|
||||
### Persona Data Model
|
||||
|
||||
```yaml
|
||||
# ~/.config/cleveragents/personas/feature-dev.yaml
|
||||
name: "feature-dev"
|
||||
description: "Feature development with Claude on main projects"
|
||||
actor: "anthropic/claude-4-sonnet"
|
||||
color: "$primary" # Textual CSS color token or hex
|
||||
|
||||
base_arguments:
|
||||
thinking_effort: "medium"
|
||||
temperature: 0.7
|
||||
max_tokens: 16384
|
||||
|
||||
scoped_projects:
|
||||
- "local/cleveragents"
|
||||
- "local/api-service"
|
||||
|
||||
scoped_plans: [] # Usually empty; populated ad-hoc
|
||||
|
||||
argument_presets:
|
||||
- name: "default" # Always present, always first
|
||||
display: "default" # Short label for PersonaBar
|
||||
overrides: {} # No overrides — uses base_arguments as-is
|
||||
|
||||
- name: "think-more"
|
||||
display: "think: high"
|
||||
overrides:
|
||||
thinking_effort: "high"
|
||||
|
||||
- name: "think-max"
|
||||
display: "think: max"
|
||||
overrides:
|
||||
thinking_effort: "max"
|
||||
temperature: 0.3 # Lower temperature for deep reasoning
|
||||
|
||||
- name: "quick"
|
||||
display: "quick"
|
||||
overrides:
|
||||
thinking_effort: "low"
|
||||
max_tokens: 4096
|
||||
|
||||
cycle_order: 1 # Position in tab-cycle list (1-indexed, 0 = not in cycle)
|
||||
```
|
||||
|
||||
### Argument Preset Resolution
|
||||
|
||||
When the user selects a preset (via `ctrl+tab`), the effective arguments are computed as:
|
||||
|
||||
```
|
||||
effective_arguments = merge(base_arguments, active_preset.overrides)
|
||||
```
|
||||
|
||||
Where `merge` is a shallow dictionary merge — preset keys override base keys, base keys not in the preset are preserved. This is the same semantics as CLI flag overrides of YAML-configured defaults.
|
||||
|
||||
The "default" preset always has empty overrides (`{}`), meaning it produces exactly `base_arguments`. This ensures the user can always return to their configured baseline.
|
||||
|
||||
### Auto-Generated Presets
|
||||
|
||||
When a persona is created (either automatically on first run or manually via the persona editor), the system inspects the actor's argument schema to auto-generate useful presets:
|
||||
|
||||
1. **Thinking effort detection** — If the actor schema includes a `thinking_effort` argument (as all built-in LLM actors do), three presets are auto-generated:
|
||||
- `"default"` → `{}` (uses base `thinking_effort`)
|
||||
- `"think: high"` → `{ thinking_effort: "high" }`
|
||||
- `"think: max"` → `{ thinking_effort: "max" }`
|
||||
|
||||
2. **Temperature detection** — If the actor schema includes a `temperature` argument, presets for `"precise"` (`{ temperature: 0.1 }`) and `"creative"` (`{ temperature: 0.9 }`) are appended.
|
||||
|
||||
3. **No matching arguments** — If the actor has no recognized argument patterns, only the `"default"` preset is created.
|
||||
|
||||
The user can modify, delete, or add presets at any time via the PersonaEditorModal.
|
||||
|
||||
### Tab Cycling Behavior
|
||||
|
||||
The `tab` key in the main chat prompt cycles through personas in the user's configured cycle list:
|
||||
|
||||
```
|
||||
tab: persona_1 → persona_2 → persona_3 → persona_1 → ...
|
||||
shift+tab: (used for sidebar cycling, NOT persona reverse-cycling)
|
||||
```
|
||||
|
||||
The cycle list is the set of personas with `cycle_order > 0`, sorted by `cycle_order`. Personas with `cycle_order: 0` exist but are not part of the tab cycle — they can only be selected explicitly via `/persona:set <name>` or through the fullscreen sidebar.
|
||||
|
||||
When cycling, the PersonaBar updates immediately to show the new persona's name, actor, and current preset. The actor binding for the session is updated — subsequent prompts use the new persona's actor and scope. Previous conversation messages remain visible (they are part of the session history, not the persona).
|
||||
|
||||
### Ctrl+Tab Preset Cycling
|
||||
|
||||
`ctrl+tab` cycles through the current persona's argument presets:
|
||||
|
||||
```
|
||||
ctrl+tab: default → think: high → think: max → quick → default → ...
|
||||
```
|
||||
|
||||
The active preset's `display` name appears in the PersonaBar. The effective arguments change immediately for the next prompt.
|
||||
|
||||
### Persona Lifecycle
|
||||
|
||||
```
|
||||
┌──────────┐ ┌──────────────┐ ┌───────────────┐ ┌──────────┐
|
||||
│ First Run │────►│ Auto-create │────►│ Active in │────►│ Modified │
|
||||
│ (no │ │ "default" │ │ session │ │ by user │
|
||||
│ personas)│ │ persona from │ │ │ │ │
|
||||
└──────────┘ │ actor select │ └───────────────┘ └──────────┘
|
||||
└──────────────┘ ▲ │
|
||||
│ │
|
||||
└─── Saved to YAML ──┘
|
||||
```
|
||||
|
||||
1. **First run**: No personas exist. Actor selection overlay creates a `"default"` persona with auto-generated presets.
|
||||
2. **Normal startup**: Last active persona is restored from `~/.config/cleveragents/tui-state.yaml`.
|
||||
3. **Manual creation**: User creates personas via PersonaEditorModal (from fullscreen sidebar or `/persona:create` command). The editor provides:
|
||||
- Actor selection (searchable list from Actor Registry)
|
||||
- Base argument editor (form fields derived from actor's argument schema)
|
||||
- Project/plan scope selection (checkboxes from Project/Plan registries)
|
||||
- Preset editor (add/remove/edit named argument overlays)
|
||||
- Cycle order assignment
|
||||
4. **Modification**: User edits an existing persona; changes are saved to the YAML file immediately.
|
||||
5. **Deletion**: User deletes a persona; the YAML file is removed. If the deleted persona was active, the next persona in the cycle list becomes active (or `"default"` if none remain).
|
||||
|
||||
### Scope Behavior
|
||||
|
||||
When a persona has scoped projects or plans:
|
||||
|
||||
1. **Every prompt** submitted in a session using that persona implicitly includes those project/plan references in the context. This is equivalent to the user typing `@project:<name>` for each scoped project in every prompt.
|
||||
2. **The `@` reference system** can add additional projects/plans for a single prompt — these are additive to the persona's scope.
|
||||
3. **The sidebar** highlights scoped projects/plans with a distinct indicator (e.g., a pin icon or accent color) to distinguish them from unscoped items.
|
||||
4. **Context assembly** (ACMS) receives the combined scope (persona scope + prompt @ references) and prioritizes those resources in the CRP for the resulting plan.
|
||||
|
||||
### PersonaBar Display
|
||||
|
||||
The PersonaBar sits directly below the prompt input and is always visible:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ❯ What would you like to do? │
|
||||
│ ▌@▐ refs ▌/▐ commands ▌!▐ shell │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
feature-dev │ anthropic/claude-4-sonnet │ think: high │ 2 projects │ $0.12
|
||||
```
|
||||
|
||||
| Segment | Content | Style | Updates When |
|
||||
|---------|---------|-------|-------------|
|
||||
| Persona name | `feature-dev` | `$text-primary` on `$primary 10%` background | `tab` cycle |
|
||||
| Actor name | `anthropic/claude-4-sonnet` | `$text-secondary` | `tab` cycle |
|
||||
| Preset label | `think: high` | `$text-warning` (differs from default), `$text-muted` (default) | `ctrl+tab` cycle |
|
||||
| Scope indicator | `2 projects` / `1 plan, 3 projects` | `$text-muted` | Persona change or `/scope:add/remove` |
|
||||
| Session cost | `$0.12` | `$secondary 70%`, right-aligned | After each actor response |
|
||||
|
||||
### Persona Storage Format
|
||||
|
||||
Personas are stored as individual YAML files in `~/.config/cleveragents/personas/`:
|
||||
|
||||
```
|
||||
~/.config/cleveragents/
|
||||
├── config.toml # Global config (existing)
|
||||
├── tui-state.yaml # TUI state (last persona, window sizes, etc.)
|
||||
└── personas/
|
||||
├── default.yaml
|
||||
├── feature-dev.yaml
|
||||
├── reviewer.yaml
|
||||
└── infra-admin.yaml
|
||||
```
|
||||
|
||||
The TUI state file tracks transient state:
|
||||
|
||||
```yaml
|
||||
# ~/.config/cleveragents/tui-state.yaml
|
||||
last_persona: "feature-dev"
|
||||
last_preset: "think: high"
|
||||
last_session_id: "01HXM8C2..."
|
||||
sidebar_state: "visible" # hidden | visible | fullscreen
|
||||
theme_override: null # null = use config.toml setting
|
||||
window_width: 180
|
||||
window_height: 50
|
||||
```
|
||||
|
||||
### Persona Export and Import
|
||||
|
||||
Personas can be shared between users or machines:
|
||||
|
||||
```
|
||||
/persona:export feature-dev # Writes feature-dev.yaml to current directory
|
||||
/persona:import ./feature-dev.yaml # Copies to personas/ directory
|
||||
```
|
||||
|
||||
Exported files are the same YAML format as stored files — no transformation needed. This enables teams to share persona configurations via version control or messaging.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Personas must never appear in the domain model, ACP protocol, database schema, or any non-TUI code path — they are a pure Presentation-layer concept
|
||||
- Every persona must have exactly one `"default"` argument preset with empty overrides; this preset cannot be deleted or renamed
|
||||
- The `cycle_order` field must be unique across all personas with `cycle_order > 0`; validation occurs on save
|
||||
- Actor references in personas must resolve to registered actors at session start; if an actor is unregistered, the persona is marked as invalid with a visible warning
|
||||
- Base arguments must conform to the referenced actor's argument schema; invalid arguments are rejected at persona save time with a validation error
|
||||
- Scoped projects and plans must reference existing entities at session start; stale references produce warnings (not errors) since projects/plans may be created or removed between TUI sessions
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- Users can switch their entire working context (actor, arguments, project scope) with a single `tab` keypress, dramatically reducing friction in multi-project workflows
|
||||
- Argument presets provide instant access to different reasoning modes (quick vs. deep thinking) without editing configuration — a single `ctrl+tab` changes the actor's behavior
|
||||
- Auto-generated presets mean first-run users get useful preset cycling without any configuration — the system discovers the actor's capabilities and creates sensible defaults
|
||||
- Persona portability (YAML export/import) enables team standardization — a lead developer can define personas for common workflows and share them with the team
|
||||
- The strict separation from the domain model means personas can evolve freely without database migrations, ACP version bumps, or backward compatibility concerns
|
||||
|
||||
### Negative
|
||||
|
||||
- The persona concept introduces a TUI-specific abstraction that has no CLI equivalent — CLI users must specify actor, arguments, and project scope explicitly on each command (this is by design, but creates a feature gap between interfaces)
|
||||
- Auto-generated presets may not match user expectations for all actor types — custom actors with unusual argument schemas may produce unhelpful or no auto-generated presets
|
||||
- Persona state is local to the machine; users on multiple machines must manually sync persona files (mitigated by export/import)
|
||||
|
||||
### Risks
|
||||
|
||||
- If the actor argument schema changes (e.g., a new version of an actor adds or removes arguments), existing persona base_arguments and preset overrides may become invalid — the system should validate on load and warn rather than fail
|
||||
- The `cycle_order` uniqueness constraint may frustrate users who want to reorder personas frequently — consider a drag-and-drop interface in the fullscreen sidebar to simplify reordering
|
||||
- Stale project/plan references in scoped_projects/scoped_plans could silently degrade context quality if the user doesn't notice the warning — consider making stale references more prominent (e.g., PersonaBar turns warning color)
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
**Store personas in the database via ACP** — This would make personas available across CLI, TUI, and Web. Rejected because it adds domain complexity for a Presentation-layer concept. Different frontends have different ergonomic needs; the CLI's stateless model doesn't benefit from personas. If cross-frontend persona sharing is needed later, it can be added as a new ACP operation without changing the persona model.
|
||||
|
||||
**Use environment variables or shell aliases instead of personas** — Users could define shell aliases like `alias ca-dev="agents --actor anthropic/claude-4 --project local/api-service"`. Rejected because this doesn't work within a TUI session (can't change the alias mid-session), doesn't support argument presets, and provides no discoverability or management UI.
|
||||
|
||||
**Actor profiles (actor-side configuration)** — Define different configurations as separate actors (e.g., `local/claude-quick`, `local/claude-deep`). Rejected because this pollutes the actor registry with configuration variants rather than distinct actors, and doesn't address project scoping.
|
||||
|
||||
**Session-level configuration only** — Allow changing actor and scope per-session via commands but without the persona bundling. Rejected because it requires multiple commands to switch context (change actor, change scope, change arguments) rather than a single tab press.
|
||||
|
||||
## Compliance
|
||||
|
||||
- **Unit tests**: Persona YAML serialization/deserialization round-trip tests for all fields
|
||||
- **Validation tests**: Verify that invalid argument schemas, missing actors, duplicate cycle orders, and missing default presets are caught at save time
|
||||
- **Integration tests**: Pilot tests verify that `tab` cycling updates the PersonaBar and subsequent prompts use the new persona's actor
|
||||
- **Preset tests**: Verify that `ctrl+tab` cycling computes effective arguments correctly (merge semantics)
|
||||
- **Auto-generation tests**: Verify that actors with `thinking_effort` arguments produce the expected presets, and actors without recognized arguments produce only the default preset
|
||||
- **State persistence tests**: Verify that `tui-state.yaml` correctly saves and restores last persona, preset, and sidebar state across TUI restarts
|
||||
- **Export/import tests**: Verify that exported YAML files are valid and importable on a different machine (no absolute paths or machine-specific references)
|
||||
@@ -0,0 +1,512 @@
|
||||
---
|
||||
adr_number: 46
|
||||
title: TUI Reference and Command System
|
||||
status_history:
|
||||
- - '2026-03-10'
|
||||
- Proposed
|
||||
- Jeffrey Phillips Freeman
|
||||
- - '2026-03-10'
|
||||
- Accepted
|
||||
- Jeffrey Phillips Freeman
|
||||
tier: 4
|
||||
authors:
|
||||
- Jeffrey Phillips Freeman
|
||||
superseded_by: null
|
||||
related_adrs:
|
||||
- number: 2
|
||||
title: Namespace System
|
||||
relationship: The @ reference notation extends the namespace naming convention with resource path addressing
|
||||
- number: 8
|
||||
title: Resource System
|
||||
relationship: Resource references in the @ notation resolve through the Resource Registry
|
||||
- number: 9
|
||||
title: Project Model
|
||||
relationship: Project references in the @ notation resolve through the Project Registry
|
||||
- number: 6
|
||||
title: Plan Lifecycle
|
||||
relationship: Plan references in the @ notation resolve through plan IDs
|
||||
- number: 14
|
||||
title: Context Management (ACMS)
|
||||
relationship: Resolved @ references become priority items in the CRP for context assembly
|
||||
- number: 44
|
||||
title: TUI Architecture and Framework
|
||||
relationship: The reference and command systems are input mechanisms within the TUI architecture
|
||||
- number: 45
|
||||
title: TUI Persona System
|
||||
relationship: Persona scoped projects/plans interact with @ reference resolution
|
||||
acceptance:
|
||||
votes_for:
|
||||
- voter: Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>
|
||||
comment: The unified @ reference notation with fuzzy search, combined with / command and ! shell prefixes, provides an ergonomic input system that bridges the multi-project domain model with natural typing patterns
|
||||
votes_against: []
|
||||
abstentions: []
|
||||
---
|
||||
## Context
|
||||
|
||||
CleverAgents manages multiple projects, plans, and resources simultaneously. In the CLI, users reference these entities by their full namespaced names or ULIDs in command arguments. In the TUI's conversational interface, users need a way to reference specific projects, plans, and resources inline within their natural-language prompts — without switching to a different screen or memorizing full identifiers.
|
||||
|
||||
Additionally, the TUI needs a command system for operations that are not natural-language prompts (session management, persona switching, configuration changes) and a shell mode for direct OS command execution. These three input modes — prompt with references, commands, and shell — must coexist in the same prompt input area with clear, unambiguous activation patterns.
|
||||
|
||||
The challenge is designing a reference notation that is:
|
||||
|
||||
1. **Ergonomic** — short enough for quick typing, with fuzzy search for discovery
|
||||
2. **Unambiguous** — parseable without context-dependent grammar rules
|
||||
3. **Hierarchical** — able to address resources within projects and decisions within plans
|
||||
4. **Integrated** — resolved references become priority items in the ACMS context assembly pipeline, not just display tokens
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- Users should be able to reference any project, plan, or resource by typing `@` followed by a partial name, with intelligent fuzzy search narrowing results as they type
|
||||
- The canonical reference format must be parseable, unique, and human-readable — it appears in the prompt text after selection and must be interpretable by both humans and the system
|
||||
- Resolved references must integrate with the ACMS Context Request Protocol (CRP) to prioritize referenced resources in context assembly — this is not merely a UI feature but a semantic operation that affects plan behavior
|
||||
- The `/` command prefix must provide a discoverable, extensible command system with tab completion — commands cover session management, persona operations, scope manipulation, and TUI configuration
|
||||
- The `!` shell prefix must provide direct OS command execution with shell history and output display in the conversation stream
|
||||
- All three input modes must be activatable and dismissable without mode confusion — the user must always know which mode they are in
|
||||
|
||||
## Decision
|
||||
|
||||
CleverAgents introduces three prompt input modes, each activated by a distinct prefix character at position 0:
|
||||
|
||||
1. **Normal mode** (default) — Natural-language prompts to the actor, with `@` inline references that trigger the Reference Picker overlay. The prompt symbol is `❯`.
|
||||
|
||||
2. **Command mode** (`/` at position 0) — TUI commands with tab completion via the Slash Command overlay. Not sent to the actor. The prompt symbol changes to `/`.
|
||||
|
||||
3. **Shell mode** (`!` at position 0) — Direct OS shell command execution. Output displayed as `ShellResult` blocks in the conversation. The prompt symbol changes to `$`.
|
||||
|
||||
Additionally, the `@` **Reference Picker** is a fuzzy-search overlay that activates when `@` is typed anywhere in a normal-mode prompt. It resolves partial input to fully qualified references using a defined notation grammar.
|
||||
|
||||
## Design
|
||||
|
||||
### Reference Notation Grammar
|
||||
|
||||
```
|
||||
reference ::= "@" qualified_ref
|
||||
| "@" fuzzy_text
|
||||
|
||||
qualified_ref ::= ref_type ":" identifier ( ":" resource_path )?
|
||||
ref_type ::= "project" | "plan"
|
||||
identifier ::= namespace_name | ulid
|
||||
namespace_name ::= ( ( server ":" )? namespace "/" )? name
|
||||
resource_path ::= resource_name ( "/" path_segment )*
|
||||
path_segment ::= <any non-whitespace character except "/">+
|
||||
resource_name ::= <registered resource name within the project>
|
||||
fuzzy_text ::= <any non-whitespace characters triggering fuzzy search>
|
||||
```
|
||||
|
||||
#### Reference Examples
|
||||
|
||||
| User Types | Canonical Expansion | What It References |
|
||||
|-----------|--------------------|--------------------|
|
||||
| `@project:cleveragents` | `@project:local/cleveragents` | The entire CleverAgents project |
|
||||
| `@project:cleveragents:git_dir/src/foo.py` | `@project:local/cleveragents:git_dir/src/foo.py` | A specific file in the project's git resource |
|
||||
| `@plan:01HXM8C2` | `@plan:01HXM8C2` | A specific plan by ULID |
|
||||
| `@plan:01HXM8C2:decision/D3` | `@plan:01HXM8C2:decision/D3` | A specific decision within a plan |
|
||||
| `@foo.py` | `@project:local/cleveragents:git_dir/src/foo.py` | Fuzzy match → expands to canonical form |
|
||||
| `@api-service` | `@project:local/api-service` | Fuzzy match → expands to project reference |
|
||||
| `@handler.py` | *(popup shows multiple matches)* | Fuzzy match with multiple candidates |
|
||||
|
||||
### Reference Picker Overlay
|
||||
|
||||
The Reference Picker is a floating panel that appears above the prompt when `@` is typed. It provides real-time fuzzy search across all registered projects, plans, and resources.
|
||||
|
||||
#### Activation and Lifecycle
|
||||
|
||||
1. User types `@` anywhere in the prompt → overlay appears with all items (most recent first)
|
||||
2. User continues typing after `@` → overlay filters results in real-time
|
||||
3. User navigates with `up`/`down` arrows → highlight moves through results
|
||||
4. User presses `enter` → selected item's canonical reference replaces the `@...` text in the prompt
|
||||
5. User presses `escape` → overlay dismisses, `@` text remains as typed (treated as literal text)
|
||||
6. User presses `tab` → switches from search mode to tree browser mode (hierarchical navigation)
|
||||
|
||||
#### Fuzzy Search Algorithm
|
||||
|
||||
The search operates across three entity types simultaneously:
|
||||
|
||||
1. **Projects**: searched by name, namespace, and description
|
||||
2. **Plans**: searched by name, ULID prefix, action name, and description
|
||||
3. **Resources**: searched by name, type, and file paths within the resource DAG
|
||||
|
||||
Matching uses a weighted fuzzy algorithm:
|
||||
|
||||
- **Exact prefix match**: weight 1.0 (e.g., `foo` matches `foo.py` at 1.0)
|
||||
- **Substring match**: weight 0.7 (e.g., `handler` matches `auth_handler.py` at 0.7)
|
||||
- **Path component match**: weight 0.8 (e.g., `auth/handler` matches `src/auth/handler.py` at 0.8)
|
||||
- **Fuzzy character match**: weight 0.4 (e.g., `ahp` matches `auth_handler.py` at 0.4)
|
||||
|
||||
Results are sorted by: (1) match weight descending, (2) recency of access descending, (3) alphabetical name ascending.
|
||||
|
||||
#### Result Display Format
|
||||
|
||||
Each result shows its type, canonical reference, and contextual information:
|
||||
|
||||
```
|
||||
┌─ Reference Picker ──────────────────────────────────────────┐
|
||||
│ @hand │
|
||||
│ ─────────────────────────────────────────────────────────── │
|
||||
│ PROJECT api-service:src/auth/handler.py │
|
||||
│ local/api-service • Python • 245 lines │
|
||||
│ │
|
||||
│ PROJECT cleveragents:git_dir/src/cli/commands/handler.py │
|
||||
│ local/cleveragents • Python • 189 lines │
|
||||
│ │
|
||||
│ PROJECT api-service:src/auth/middleware/handler_base.py │
|
||||
│ local/api-service • Python • 67 lines │
|
||||
│ │
|
||||
│ PLAN fix-auth-handler (01HXM8C2...) │
|
||||
│ Phase: Execute • Actor: claude-4-sonnet │
|
||||
│ │
|
||||
│ ─────────────────────────────────────────────────────────── │
|
||||
│ enter Select │ tab Tree │ ctrl+p Projects │ ctrl+l Plans │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Filter Shortcuts
|
||||
|
||||
While the Reference Picker is open:
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `ctrl+p` | Filter to projects only |
|
||||
| `ctrl+l` | Filter to plans only |
|
||||
| `ctrl+r` | Filter to resources only (across all projects) |
|
||||
| `ctrl+a` | Clear filter (show all types) |
|
||||
| `tab` | Switch to tree browser mode |
|
||||
| `escape` | Dismiss overlay |
|
||||
|
||||
#### Tree Browser Mode
|
||||
|
||||
When the user presses `tab` in the Reference Picker, it switches from search mode to tree browser mode. This shows a navigable tree:
|
||||
|
||||
```
|
||||
┌─ Reference Picker (Tree) ───────────────────────────────────┐
|
||||
│ │
|
||||
│ ▼ Projects │
|
||||
│ ▼ local/cleveragents │
|
||||
│ ▼ git_dir (git-checkout) │
|
||||
│ ▸ src/ │
|
||||
│ ▸ tests/ │
|
||||
│ ▸ docs/ │
|
||||
│ ─ pyproject.toml │
|
||||
│ ─ README.md │
|
||||
│ ▸ database (sqlite-db) │
|
||||
│ ▸ local/api-service │
|
||||
│ ▸ local/frontend-app │
|
||||
│ ▼ Plans │
|
||||
│ ▸ fix-auth-bug (01HXM8C2) │
|
||||
│ ▸ refactor-models (01HXM9D3) │
|
||||
│ │
|
||||
│ enter Select │ tab Search │ space Expand │ / Filter │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Navigation uses standard tree controls: `up`/`down` to move, `space` or `right` to expand, `left` to collapse, `enter` to select.
|
||||
|
||||
### ACMS Integration
|
||||
|
||||
When a prompt contains resolved `@` references, those references are translated into CRP (Context Request Protocol) directives before the prompt is sent to the actor:
|
||||
|
||||
1. **Project references** (`@project:local/api-service`) → the project is added to the plan's project scope, and all its resources receive elevated priority in context assembly
|
||||
2. **Resource path references** (`@project:local/api-service:src/auth/handler.py`) → the specific resource and path are marked as `focus` items in the CRP, receiving the highest priority tier in the context budget
|
||||
3. **Plan references** (`@plan:01HXM8C2`) → the plan's decision tree and current state are included as context, enabling cross-plan awareness
|
||||
4. **Decision references** (`@plan:01HXM8C2:decision/D3`) → the specific decision's context snapshot, rationale, and downstream impact are included as focus items
|
||||
|
||||
This integration means that `@` references are not merely display annotations — they semantically direct the ACMS to prioritize specific information when assembling the actor's context window.
|
||||
|
||||
#### Priority Interaction with Persona Scope
|
||||
|
||||
The effective scope for context assembly is the union of:
|
||||
|
||||
1. **Persona scoped projects/plans** (always present, baseline priority)
|
||||
2. **Prompt @ references** (additive, elevated priority)
|
||||
|
||||
When a resource is both persona-scoped and explicitly `@`-referenced, the explicit reference takes precedence for priority elevation. This means `@`-referencing a file in an already-scoped project still has the effect of focusing the context on that specific file.
|
||||
|
||||
### Command System (/ Prefix)
|
||||
|
||||
Commands are TUI operations that execute immediately without being sent to the actor. They are activated by typing `/` as the first character of the prompt.
|
||||
|
||||
#### Slash Command Overlay
|
||||
|
||||
When `/` is typed at position 0, the Slash Command overlay appears with filterable command list:
|
||||
|
||||
```
|
||||
┌─ Commands ──────────────────────────────────────────────────┐
|
||||
│ /se │
|
||||
│ ─────────────────────────────────────────────────────────── │
|
||||
│ /session:create Create a new session tab │
|
||||
│ /session:list Show all sessions │
|
||||
│ /session:show Show session details │
|
||||
│ /session:switch Switch to session by ID │
|
||||
│ /session:close Close current session │
|
||||
│ /session:delete Delete a saved session │
|
||||
│ /session:rename Rename current session │
|
||||
│ /session:export Export session to file │
|
||||
│ /session:import Import session from file │
|
||||
│ /settings Open settings screen │
|
||||
│ │
|
||||
│ enter Execute │ tab Complete │ escape Dismiss │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Complete Command Reference
|
||||
|
||||
**Session Commands** — *mirrors CLI `agents session <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/session:create` | `[--persona <name>]` | Create a new session tab, optionally with a specific persona |
|
||||
| `/session:list` | — | Display all sessions with IDs, personas, and last activity |
|
||||
| `/session:show <id>` | `<id>` (required) | Show session details (persona, actor, message count, cost) |
|
||||
| `/session:switch <id>` | `<id>` (required) | Switch to a session by its ID or tab index |
|
||||
| `/session:close` | `[--force]` | Close the current session tab (confirms if conversation exists) |
|
||||
| `/session:delete <id>` | `<id>` (required), `[--yes/-y]` | Permanently delete a saved session (with confirmation) |
|
||||
| `/session:rename <name>` | `<name>` (required) | Rename the current session's display label |
|
||||
| `/session:export [path]` | `[path]` (optional, defaults to `./<session-name>.json`) | Export the current session's conversation history to a JSON file |
|
||||
| `/session:import <path>` | `<path>` (required) | Import a session from a previously exported JSON file |
|
||||
|
||||
**Persona Commands**
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/persona:list` | — | Display all personas with their actors, scopes, and cycle positions |
|
||||
| `/persona:set <name>` | `<name>` (required) | Switch to a persona by name (updates actor and scope immediately) |
|
||||
| `/persona:create` | — | Open the PersonaEditorModal for creating a new persona |
|
||||
| `/persona:edit [name]` | `[name]` (optional, defaults to current) | Open the PersonaEditorModal for an existing persona |
|
||||
| `/persona:delete <name>` | `<name>` (required) | Delete a persona (confirms before deletion) |
|
||||
| `/persona:export <name>` | `<name>` (required) | Export a persona's YAML to the current directory |
|
||||
| `/persona:import <path>` | `<path>` (required) | Import a persona from a YAML file |
|
||||
|
||||
**Scope Commands**
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/scope:add <ref>` | `<ref>` (required, e.g., `project:local/api-service`) | Add a project or plan to the current session's scope (additive to persona scope) |
|
||||
| `/scope:remove <ref>` | `<ref>` (required) | Remove a project or plan from the current session's scope |
|
||||
| `/scope:clear` | — | Clear all session-level scope additions (persona scope remains) |
|
||||
| `/scope:show` | — | Display the effective scope (persona + session additions) |
|
||||
|
||||
**Plan Commands** — *mirrors CLI `agents plan <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/plan:use <action> [projects...]` | `<action>` (required), `[projects...]`, `[--arg/-a <key=value>]...` `[--automation-profile <name>]` `[--invariant <text>]...` | Start a new plan from an action |
|
||||
| `/plan:list` | `[--phase <phase>]` `[--state <state>]` `[--project <name>]` `[--action <name>]` | List plans, optionally filtered |
|
||||
| `/plan:status [id]` | `[id]` (optional, defaults to current plan) | Show detailed plan status in a conversation block |
|
||||
| `/plan:tree <id>` | `<id>` (required), `[--show-superseded]` `[--depth <n>]` | Show the plan's decision tree in a conversation block |
|
||||
| `/plan:execute [id]` | `[id]` (optional, defaults to current plan) | Execute a plan that has completed strategize phase |
|
||||
| `/plan:apply [id]` | `[id]` (optional, defaults to current plan) | Apply a completed plan (confirms before apply) |
|
||||
| `/plan:cancel <id>` | `<id>` (required), `[--reason/-r <text>]` | Cancel a running plan (confirms before cancellation) |
|
||||
| `/plan:diff <id>` | `<id>` (required), `[--correction]` | Show the plan's diff in a DiffView block |
|
||||
| `/plan:correct <did>` | `<did>` (required), `--mode/-m revert\|append` (required), `--guidance/-g <text>` (required), `[--yes/-y]` | Correct a decision in a plan |
|
||||
| `/plan:resume <id>` | `<id>` (required), `[--dry-run]` | Resume a paused or failed plan |
|
||||
| `/plan:revert <id>` | `<id>` (required), `[--to-phase <phase>]` `[--reason/-r <text>]` | Revert a plan to an earlier phase |
|
||||
| `/plan:rollback <id> <checkpoint>` | both required, `[--yes/-y]` | Rollback plan to a specific checkpoint |
|
||||
| `/plan:explain <did>` | `<did>` (required), `[--show-context]` `[--show-reasoning]` | Explain a decision's rationale and context |
|
||||
| `/plan:errors <id>` | `<id>` (required) | Show plan errors in a conversation block |
|
||||
| `/plan:artifacts <id>` | `<id>` (required) | Show plan artifacts in a conversation block |
|
||||
| `/plan:inspect [id]` | `[id]` (optional, defaults to current plan) | Open the PlanDetailModal for a plan |
|
||||
|
||||
**Project Commands** — *mirrors CLI `agents project <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/project:list` | `[--namespace/-n <ns>]` | List all registered projects |
|
||||
| `/project:create <name>` | `<name>` (required), `[--description/-d <text>]` `[--resource/-r <name>]...` | Create a new project |
|
||||
| `/project:show <name>` | `<name>` (required) | Show project details in a conversation block |
|
||||
| `/project:delete <name>` | `<name>` (required), `[--yes/-y]` | Delete a project (with confirmation) |
|
||||
| `/project:inspect <name>` | `<name>` (required) | Open the ProjectDetailModal for a project |
|
||||
| `/project:context:show <name>` | `<name>` (required), `[--view/-v <view>]` | Show project context configuration |
|
||||
|
||||
**Actor Commands** — *mirrors CLI `agents actor <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/actor:list` | — | List all registered actors |
|
||||
| `/actor:show <name>` | `<name>` (required) | Show actor details (configuration, argument schema, capabilities) |
|
||||
| `/actor:set-default <name>` | `<name>` (required) | Set the default actor for new sessions |
|
||||
|
||||
**Resource Commands** — *mirrors CLI `agents resource <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/resource:list [project]` | `[project]` (optional), `[--type/-t <type>]` | List resources, optionally scoped to a project or type |
|
||||
| `/resource:show <name>` | `<name>` (required) | Show resource details (type, properties, DAG position) |
|
||||
| `/resource:tree [project]` | `[project]` (optional), `[--depth/-d <n>]` | Show the resource DAG as a tree |
|
||||
| `/resource:inspect <name>` | `<name>` (required) | Show detailed resource info with children and validations |
|
||||
|
||||
**Automation Profile Commands** — *mirrors CLI `agents automation-profile <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/profile:list` | — | List available automation profiles |
|
||||
| `/profile:show <name>` | `<name>` (required) | Show automation profile details (thresholds, safety settings) |
|
||||
|
||||
**Invariant Commands** — *mirrors CLI `agents invariant <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/invariant:list` | `[--project <name>]` `[--plan <id>]` `[--global]` `[--effective]` | List invariant constraints |
|
||||
| `/invariant:add <text>` | `<text>` (required), `[--global]` `[--project <name>]` `[--plan <id>]` | Add an invariant constraint |
|
||||
| `/invariant:remove <id>` | `<id>` (required), `[--yes/-y]` | Remove an invariant constraint |
|
||||
|
||||
**Config Commands** — *mirrors CLI `agents config <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/config:list [pattern]` | `[pattern]` (optional glob) | List configuration keys and values |
|
||||
| `/config:get <key>` | `<key>` (required) | Show a configuration value |
|
||||
| `/config:set <key> <value>` | both required | Set a configuration value |
|
||||
|
||||
**Tool Commands** — *mirrors CLI `agents tool <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/tool:list` | `[--namespace/-n <ns>]` `[--type/-t <type>]` | List registered tools |
|
||||
| `/tool:show <name>` | `<name>` (required) | Show tool details (capabilities, argument schema) |
|
||||
|
||||
**Skill Commands** — *mirrors CLI `agents skill <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/skill:list` | `[--namespace/-n <ns>]` | List registered skills |
|
||||
| `/skill:show <name>` | `<name>` (required) | Show skill details and provided tools |
|
||||
|
||||
**TUI Commands** — *TUI-only utility commands*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/clear` | — | Clear the conversation display (does not delete session history) |
|
||||
| `/theme [name]` | `[name]` (optional, shows current if omitted) | Switch the TUI color theme |
|
||||
| `/settings` | — | Open the SettingsScreen |
|
||||
| `/help [command]` | `[command]` (optional) | Show help for a specific command, or list all commands |
|
||||
| `/about` | — | Show CleverAgents version, build info, and system status |
|
||||
| `/debug` | — | Toggle debug mode (shows ACP events, widget boundaries, performance metrics) |
|
||||
|
||||
#### Command Extensibility
|
||||
|
||||
Actors can register additional slash commands via ACP. When an actor session is established, the actor may provide an `AvailableCommandsUpdate` message listing actor-specific commands. These appear in the Slash Command overlay with the actor's namespace prefix:
|
||||
|
||||
```
|
||||
/claude:explain Ask Claude to explain its reasoning
|
||||
/claude:summarize Summarize the conversation so far
|
||||
/claude:focus <topic> Focus subsequent responses on a topic
|
||||
```
|
||||
|
||||
### Shell Mode (! Prefix)
|
||||
|
||||
Shell mode provides direct OS command execution from the TUI prompt.
|
||||
|
||||
#### Activation and Behavior
|
||||
|
||||
1. User types `!` as the first character → prompt symbol changes from `❯` to `$`, prompt background tints to indicate shell mode
|
||||
2. User types a shell command (e.g., `!git status`)
|
||||
3. User presses `enter` → command executes on the host OS via subprocess
|
||||
4. Output appears as a `ShellResult` block in the conversation stream
|
||||
5. After execution, the prompt returns to normal mode
|
||||
|
||||
#### Shell Mode Characteristics
|
||||
|
||||
| Aspect | Behavior |
|
||||
|--------|----------|
|
||||
| **Working directory** | The host OS current directory (where `agents` was launched), NOT a project resource directory |
|
||||
| **History** | Separate history from prompt history; navigable with `up`/`down` while in shell mode |
|
||||
| **Tab completion** | File/directory path completion from the host filesystem |
|
||||
| **Syntax highlighting** | Bash/shell syntax highlighting in the prompt |
|
||||
| **Dangerous command detection** | Commands matching destructive patterns (e.g., `rm -rf /`, `git push --force`) are highlighted in `$error` color with a confirmation prompt |
|
||||
| **Long-running commands** | Output streams in real-time; `ctrl+c` sends SIGINT to the subprocess |
|
||||
| **Exit** | `escape` exits shell mode back to normal prompt mode (if no command is being typed); otherwise clears the current input |
|
||||
|
||||
#### Shell Result Display
|
||||
|
||||
```
|
||||
$ git status
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ On branch main │
|
||||
│ Your branch is up to date with 'origin/main'. │
|
||||
│ │
|
||||
│ Changes not staged for commit: │
|
||||
│ modified: src/auth/handler.py │
|
||||
│ modified: src/auth/middleware.py │
|
||||
│ │
|
||||
│ Untracked files: │
|
||||
│ src/auth/limiter.py │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Shell results have a `$primary` left border and `$foreground 4%` background, distinguishing them from actor responses and user prompts.
|
||||
|
||||
### Input Mode Summary
|
||||
|
||||
| First Character | Mode | Prompt Symbol | Sent To | Overlay |
|
||||
|-----------------|------|---------------|---------|---------|
|
||||
| *(any other)* | Normal | `❯` | Actor via ACP `session.tell` | — |
|
||||
| `@` *(inline)* | Normal + Reference | `❯` | Actor (with resolved references as CRP directives) | ReferencePickerOverlay |
|
||||
| `/` | Command | `/` | TUI command processor (local) | SlashCommandOverlay |
|
||||
| `!` | Shell | `$` | Host OS subprocess | — |
|
||||
|
||||
### Multi-line Input
|
||||
|
||||
In normal mode, the prompt supports multi-line input:
|
||||
|
||||
- `shift+enter` or `ctrl+j` inserts a newline (prompt symbol changes to `☰`)
|
||||
- In multi-line mode, `shift+enter` or `ctrl+j` submits the prompt
|
||||
- `escape` in multi-line mode with no overlay visible returns to single-line mode (discarding extra lines if empty)
|
||||
|
||||
In shell mode, multi-line input is supported via backslash continuation (`\` at end of line) following standard shell conventions.
|
||||
|
||||
Command mode (`/`) is always single-line.
|
||||
|
||||
## Constraints
|
||||
|
||||
- The `@` character must always trigger the Reference Picker when typed in normal mode, regardless of cursor position — it is never treated as a literal character in prompts (users who need a literal `@` can use `\@`)
|
||||
- The `/` character triggers command mode only at position 0 of an empty or beginning-of-line prompt — `/` in the middle of text is literal
|
||||
- The `!` character triggers shell mode only at position 0 — `!` in the middle of text is literal
|
||||
- Reference resolution must complete within 200ms for interactive responsiveness; the fuzzy search index must be pre-built and incrementally updated
|
||||
- All `/` commands must have consistent argument parsing: positional arguments are space-separated, named arguments use `--flag value` syntax, matching the CLI convention
|
||||
- Shell mode commands execute with the same user permissions as the `agents` process — no privilege escalation is possible
|
||||
- The Reference Picker must not block the main event loop — search operations run in a worker thread with debounced input (100ms)
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- The `@` reference system provides a natural, conversational way to direct the actor's attention to specific resources — `"review @project:api-service:src/auth/handler.py"` reads naturally and has precise semantic effect on context assembly
|
||||
- Fuzzy search means users rarely need to type full canonical references — `@handler.py` is sufficient when there's a clear best match
|
||||
- The `/` command system provides a discoverable, tab-completable interface for all TUI operations — users can discover available commands without memorizing hotkeys
|
||||
- Shell mode (`!`) provides a convenient escape hatch for quick OS operations without leaving the TUI
|
||||
- The three-mode design (normal/command/shell) provides clear separation of concerns with unambiguous activation — there is never confusion about which mode a keystroke will be interpreted in
|
||||
- ACMS integration means `@` references have real semantic effect: they don't just annotate the prompt, they direct the context assembly pipeline to prioritize the referenced resources
|
||||
|
||||
### Negative
|
||||
|
||||
- The `@` character cannot be used literally in prompts without escaping (`\@`) — this may confuse users who habitually use `@` in email addresses or social media handles in their prompts
|
||||
- The command namespace (`/session:*`, `/persona:*`, etc.) may become crowded as features are added — the colon-separated namespace helps, but discoverability depends on good tab completion
|
||||
- Shell mode operates on the host OS, not on project resources — users expecting `!ls` to list files in a project resource will need to understand the distinction
|
||||
|
||||
### Risks
|
||||
|
||||
- Fuzzy search performance may degrade with very large resource registries (thousands of resources across many projects) — the search index should be bounded and prioritize recently-accessed resources
|
||||
- Reference resolution ambiguity: when `@foo.py` matches files in multiple projects, the user must select from the popup — this is extra friction compared to single-project tools where `@foo.py` is always unambiguous
|
||||
- Actor-provided commands via `AvailableCommandsUpdate` could conflict with built-in commands — the system must enforce that actor commands are always prefixed with the actor's namespace (e.g., `/claude:*`)
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
**Hashtag notation (`#`) instead of `@`** — Some systems use `#` for references. Rejected because `@` is more universally associated with "mentioning" or "referencing" entities (email, social media, IDE go-to-symbol) and `#` is commonly used for comments in code-adjacent contexts, which could cause confusion.
|
||||
|
||||
**Separate reference input field** — A dedicated input area for adding references, separate from the prompt. Rejected because it breaks the natural flow of typing a prompt — the user would need to switch focus, add references, switch back, and type the prompt. Inline `@` references keep everything in one place.
|
||||
|
||||
**IDE-style Ctrl+Space completion** — Trigger a general completion menu with `ctrl+space` that includes references, commands, and shell. Rejected because it conflates three distinct input modes into one mechanism, making the UI less predictable. The distinct prefix characters (`@`, `/`, `!`) provide clear affordances.
|
||||
|
||||
**SQL-like FROM clause** — Require explicit scope declaration before the prompt: `FROM project:api-service SELECT review auth module`. Rejected as overly formal for a conversational interface. The `@` inline notation is more natural.
|
||||
|
||||
## Compliance
|
||||
|
||||
- **Grammar tests**: Parser tests verify that all reference notation examples in this ADR parse correctly to their canonical form
|
||||
- **Fuzzy search tests**: Search algorithm tests verify ranking correctness (exact > substring > path > fuzzy) with fixed test datasets
|
||||
- **Resolution tests**: Integration tests verify that `@` references resolve through the actual Project/Plan/Resource registries via ACP
|
||||
- **ACMS integration tests**: Verify that resolved references produce correct CRP directives and that referenced resources appear in assembled context with elevated priority
|
||||
- **Command tests**: Every `/` command has a test that simulates input and verifies the expected TUI action (screen push, state change, conversation block)
|
||||
- **Shell mode tests**: Verify that `!` commands execute via subprocess, output appears as ShellResult blocks, and `ctrl+c` interrupts running commands
|
||||
- **Performance tests**: Fuzzy search benchmarks verify < 200ms response time with 10,000+ resources in the index
|
||||
- **Overlay tests**: Pilot tests verify that overlays appear on trigger, filter correctly as the user types, and dismiss on escape/selection
|
||||
- **Escaping tests**: Verify that `\@` produces a literal `@` in the prompt text and does not trigger the Reference Picker
|
||||
@@ -28641,6 +28641,841 @@ The TUI should show:
|
||||
|
||||
And should later allow exporting the tree as image (PNG) or JSON for other visualization tools.
|
||||
|
||||
|
||||
#### TUI Architecture Overview
|
||||
|
||||
!!! adr "Architecture Decision"
|
||||
The TUI architecture, framework selection (Textual >= 1.0), screen hierarchy, TuiMaterializer integration, and ACP event subscription model are defined in [ADR-044: TUI Architecture and Framework](adr/ADR-044-tui-architecture-and-framework.md).
|
||||
|
||||
The TUI (Terminal User Interface) is the second Presentation-layer surface for CleverAgents, built on **Textual** (>= 1.0). It provides real-time plan monitoring, multi-session management, interactive decision tree navigation, and rich conversation with actors — capabilities impractical in the stateless CLI.
|
||||
|
||||
The TUI communicates with the Application layer exclusively through ACP (ADR-026). It subscribes to ACP events for real-time updates and uses the existing Output Rendering Framework (ADR-021) via a `TuiMaterializer` that maps `ElementHandle` events to Textual widget operations — enabling all CLI command producers to render in the TUI without modification.
|
||||
|
||||
Key architectural principles:
|
||||
|
||||
- **Direct-to-chat launch** — no launcher screen; opens directly to the main chat interface
|
||||
- **Right-side collapsible sidebar** — three states cycled by `shift+tab`: hidden → visible → fullscreen
|
||||
- **Multi-session tabs** — independent sessions with separate personas, conversations, and ACP bindings
|
||||
- **Escape-cascading navigation** — `escape` always moves toward the main screen from any state
|
||||
- **Keyboard-first** — every operation achievable without a mouse; mouse is supplementary
|
||||
- **Single UI codebase** — the same Textual widget tree serves standalone TUI, Web (via Textual Web), and IDE plugin
|
||||
|
||||
|
||||
##### MainScreen Layout — Sidebar Hidden
|
||||
|
||||
When the sidebar is hidden, the conversation takes the full terminal width:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌──────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│<span class="tui-rainbow">═══════════════════════════════════════════ ◆ ═══════════════════════════════════════════</span> │
|
||||
│ <span class="tui-dim">Session 1</span> ┃ <span class="tui-primary-u">Session 2</span> ┃ <span class="tui-dim">Session 3</span> │
|
||||
│ ┗━━━━━━━━━━━━┛ │
|
||||
├──────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ <span class="tui-secondary">┃</span> <span class="tui-bold">You</span> <span class="tui-dim">14:01</span> │
|
||||
│ <span class="tui-secondary">┃</span> Can you review the auth module in the │
|
||||
│ <span class="tui-secondary">┃</span> API service? │
|
||||
│ <span class="tui-secondary">┃</span> <span class="tui-primary-u">@project:api-service:src/auth/handler.py</span> │
|
||||
│ <span class="tui-secondary">┃</span> │
|
||||
│ <span class="tui-dim">│</span> │
|
||||
│ <span class="tui-dim">│</span> <span class="tui-bold">Actor</span> <span class="tui-dim">14:02</span> │
|
||||
│ <span class="tui-dim">│</span> I'll review the authentication handler. │
|
||||
│ <span class="tui-dim">│</span> Let me analyze the code structure and │
|
||||
│ <span class="tui-dim">│</span> identify potential issues. │
|
||||
│ <span class="tui-dim">│</span> │
|
||||
│ <span class="tui-dim">│</span> 🔧 local/code-analysis <span class="tui-success">✔</span> <span class="tui-dim">▶</span> │
|
||||
│ <span class="tui-dim">│</span> │
|
||||
│ <span class="tui-dim">│</span> Found 3 issues in the auth module: │
|
||||
│ <span class="tui-dim">│</span> │
|
||||
│ <span class="tui-dim">│</span> 1. **Missing rate limiting** │
|
||||
│ <span class="tui-dim">│</span> 2. **JWT token** not validated │
|
||||
│ <span class="tui-dim">│</span> 3. **Session cleanup** missing │
|
||||
│ <span class="tui-dim">│</span> │
|
||||
│ <span class="tui-dim">│</span> 🔧 local/file-read <span class="tui-success">✔</span> <span class="tui-dim">▼</span> │
|
||||
│ <span class="tui-dim">│</span> ┌─ src/auth/handler.py ─────────────┐ │
|
||||
│ <span class="tui-dim">│</span> │ 45 │ def login(self, req): │ │
|
||||
│ <span class="tui-dim">│</span> │ 46 │ creds = extract(req) │ │
|
||||
│ <span class="tui-dim">│</span> │ 47 │ return auth(creds) │ │
|
||||
│ <span class="tui-dim">│</span> └──────────────────────────────────┘ │
|
||||
│ │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-success">Agent connected</span> │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ ┌─────────────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ <span class="tui-bold-primary">❯</span> What would you like to do? │ │
|
||||
│ │ <span class="tui-secondary">▌@▐</span> refs <span class="tui-secondary">▌/▐</span> commands <span class="tui-secondary">▌!▐</span> shell │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ <span class="tui-primary">feature-dev</span> │ <span class="tui-dim">claude-4-sonnet</span> │ <span class="tui-warning">think: high</span> │ <span class="tui-dim">2 projects</span> │ $0.12 │
|
||||
├──────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ <span class="tui-dim">F1</span> Help │ <span class="tui-dim">shift+tab</span> Sidebar │ <span class="tui-dim">tab</span> Persona │ <span class="tui-dim">ctrl+tab</span> Preset │ <span class="tui-dim">ctrl+s</span> Sessions │ <span class="tui-dim">ctrl+q</span> Quit │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
Key elements in this layout:
|
||||
|
||||
- **Throbber** (top edge): rainbow gradient bar, visible only when the actor is processing. Collapses to zero height when idle.
|
||||
- **Session Tabs** (below throbber): tab bar with underline indicator on the active session. Hidden when only one session exists. Session labels show state icons: `⌛` (actor working), `❯` (awaiting input), plain (idle).
|
||||
- **Conversation** (center): scrollable message stream with block cursor navigation.
|
||||
- **Prompt** (bottom): PromptTextArea with mode-dependent symbol (❯ normal, $ shell, ☰ multi-line), overlays for @ references and / commands, and the PersonaBar.
|
||||
- **Footer**: context-sensitive hotkey reference, always visible.
|
||||
|
||||
|
||||
##### MainScreen Layout — Sidebar Visible
|
||||
|
||||
When the sidebar is visible (shift+tab from hidden), it docks to the right at 32-40 chars wide:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌──────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│<span class="tui-rainbow">═══════════════════════════════════════════ ◆ ═══════════════════════════════════════════</span> │
|
||||
│ <span class="tui-dim">Session 1</span> ┃ <span class="tui-primary-u">Session 2</span> ┃ <span class="tui-dim">Session 3</span> │
|
||||
│ ┗━━━━━━━━━━━━┛ │
|
||||
├─────────────────────────────────────────────────────────────┬────────────────────────────────┤
|
||||
│ │ <span class="tui-bold">▼ PLANS</span> │
|
||||
│ <span class="tui-secondary">┃</span> <span class="tui-bold">You</span> <span class="tui-dim">14:01</span> │ ┌────────────────────────────┐ │
|
||||
│ <span class="tui-secondary">┃</span> Can you review the auth module in the │ │ <span class="tui-bold">►</span> fix-auth-bug │ │
|
||||
│ <span class="tui-secondary">┃</span> API service? │ │ Phase: Execute <span class="tui-success">●●●</span><span class="tui-dim">○</span> │ │
|
||||
│ <span class="tui-secondary">┃</span> <span class="tui-primary-u">@project:api-service:src/auth/handler.py</span> │ │ Profile: trusted │ │
|
||||
│ <span class="tui-secondary">┃</span> │ │ Actor: claude-4-sonnet │ │
|
||||
│ <span class="tui-dim">│</span> │ │ Cost: $0.08 │ │
|
||||
│ <span class="tui-dim">│</span> <span class="tui-bold">Actor</span> <span class="tui-dim">14:02</span> │ │ │ │
|
||||
│ <span class="tui-dim">│</span> I'll review the authentication handler. │ │ <span class="tui-bold">►</span> refactor-models │ │
|
||||
│ <span class="tui-dim">│</span> Let me analyze the code structure and │ │ Phase: Strategize <span class="tui-primary">●</span><span class="tui-dim">○○○</span> │ │
|
||||
│ <span class="tui-dim">│</span> identify potential issues. │ │ Profile: cautious │ │
|
||||
│ <span class="tui-dim">│</span> │ │ Depth: 2 (3 subplans) │ │
|
||||
│ <span class="tui-dim">│</span> 🔧 local/code-analysis <span class="tui-success">✔</span> <span class="tui-dim">▶</span> │ │ Actor: gpt-4o │ │
|
||||
│ <span class="tui-dim">│</span> │ │ ├─ users (execute) │ │
|
||||
│ <span class="tui-dim">│</span> Found 3 issues in the auth module: │ │ ├─ orders (strategize) │ │
|
||||
│ <span class="tui-dim">│</span> │ │ └─ auth (pending) │ │
|
||||
│ <span class="tui-dim">│</span> 1. **Missing rate limiting** │ │ │ │
|
||||
│ <span class="tui-dim">│</span> 2. **JWT token** not validated │ │ <span class="tui-dim">◌</span> update-deps │ │
|
||||
│ <span class="tui-dim">│</span> 3. **Session cleanup** missing │ │ Phase: Idle │ │
|
||||
│ <span class="tui-dim">│</span> │ │ Profile: auto │ │
|
||||
│ <span class="tui-dim">│</span> 🔧 local/file-read <span class="tui-success">✔</span> <span class="tui-dim">▼</span> │ └────────────────────────────┘ │
|
||||
│ <span class="tui-dim">│</span> ┌─ src/auth/handler.py ─────────────┐ │ │
|
||||
│ <span class="tui-dim">│</span> │ 45 │ def login(self, req): │ │ <span class="tui-bold">▼ PROJECTS</span> │
|
||||
│ <span class="tui-dim">│</span> │ 46 │ creds = extract(req) │ │ ┌────────────────────────────┐ │
|
||||
│ <span class="tui-dim">│</span> │ 47 │ return auth(creds) │ │ │ ◆ cleveragents [2p] │ │
|
||||
│ <span class="tui-dim">│</span> └──────────────────────────────────┘ │ │ ◆ api-service [1p] │ │
|
||||
│ │ │ frontend-app [0p] │ │
|
||||
│─────────────────────────────────────────────────────────────│ │ infra-terraform [0p] │ │
|
||||
│ <span class="tui-success">Agent connected</span> │ └────────────────────────────┘ │
|
||||
│─────────────────────────────────────────────────────────────│ │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │ │
|
||||
│ │ <span class="tui-bold-primary">❯</span> What would you like to do? │ │ │
|
||||
│ │ <span class="tui-secondary">▌@▐</span> refs <span class="tui-secondary">▌/▐</span> commands <span class="tui-secondary">▌!▐</span> shell │ │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │ │
|
||||
│ <span class="tui-primary">feature-dev</span> │ <span class="tui-dim">claude-4-sonnet</span> │ <span class="tui-warning">think: med</span> │ <span class="tui-dim">2 proj</span> │ $0.12 │ │
|
||||
├─────────────────────────────────────────────────────────────┴────────────────────────────────┤
|
||||
│ <span class="tui-dim">F1</span> Help │ <span class="tui-dim">shift+tab</span> Sidebar │ <span class="tui-dim">tab</span> Persona │ <span class="tui-dim">ctrl+tab</span> Preset │ <span class="tui-dim">ctrl+s</span> Sessions │ <span class="tui-dim">ctrl+q</span> Quit │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
Key elements in this layout:
|
||||
|
||||
- **Throbber** (top edge): rainbow gradient bar, visible only when the actor is processing. Collapses to zero height when idle.
|
||||
- **Session Tabs** (below throbber): tab bar with underline indicator on the active session. Hidden when only one session exists. Session labels show state icons: `⌛` (actor working), `❯` (awaiting input), plain (idle).
|
||||
- **Conversation** (center): scrollable message stream with block cursor navigation.
|
||||
- **Sidebar** (right, 32-40 chars wide): Plans panel and Projects panel in collapsible containers.
|
||||
- **Prompt** (bottom of left panel): same prompt components as hidden mode, constrained to the conversation column width.
|
||||
- **PersonaBar** (below prompt): shows persona name, actor, preset, scope, and cost.
|
||||
|
||||
|
||||
##### MainScreen Layout — Sidebar Fullscreen
|
||||
|
||||
Fullscreen mode (shift+tab from visible) covers the entire terminal for plan/project/persona management:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌──────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ <span class="tui-bold-primary">PLANS & PROJECTS BROWSER </span> │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-bold">PLANS</span> │ <span class="tui-bold">PROJECTS</span> │
|
||||
│ ┌───────────────────────────────────────────┐ │ ┌──────────────────────────────────────────┐ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ [x] <span class="tui-bold">► fix-auth-bug</span> │ │ │ [x] <span class="tui-bold">cleveragents</span> │ │
|
||||
│ │ Phase: Execute <span class="tui-success">●●●</span><span class="tui-dim">○</span> │ │ │ Namespace: local/ │ │
|
||||
│ │ State: in_progress │ │ │ Resources: 5 │ │
|
||||
│ │ Profile: trusted │ │ │ Plans: 2 active │ │
|
||||
│ │ Actor: anthropic/claude-4-sonnet │ │ │ Invariants: 3 │ │
|
||||
│ │ Started: 2m ago Cost: $0.08 │ │ │ Validations: 2 │ │
|
||||
│ │ Decisions: 6 (4 done, 2 pending) │ │ │ │ │
|
||||
│ │ │ │ │ [ ] <span class="tui-bold">api-service</span> │ │
|
||||
│ │ [ ] <span class="tui-bold">refactor-models</span> │ │ │ Namespace: local/ │ │
|
||||
│ │ Phase: Strategize <span class="tui-primary">●</span><span class="tui-dim">○○○</span> │ │ │ Resources: 3 │ │
|
||||
│ │ State: in_progress │ │ │ Plans: 1 active │ │
|
||||
│ │ Profile: cautious │ │ │ Invariants: 1 │ │
|
||||
│ │ Actor: openai/gpt-4o │ │ │ Validations: 4 │ │
|
||||
│ │ Started: 5m ago Cost: $0.03 │ │ │ │ │
|
||||
│ │ Decisions: 3 (2 done, 1 active) │ │ │ [ ] <span class="tui-bold">frontend-app</span> │ │
|
||||
│ │ Subplans: 3 (depth 2) │ │ │ Namespace: local/ │ │
|
||||
│ │ ├─ users Execute <span class="tui-success">●●●</span><span class="tui-dim">○</span> │ │ │ Resources: 2 │ │
|
||||
│ │ ├─ orders Strategize <span class="tui-primary">●</span><span class="tui-dim">○○○</span> │ │ │ Plans: 0 │ │
|
||||
│ │ └─ auth Pending │ │ │ │ │
|
||||
│ │ │ │ │ [ ] <span class="tui-bold">infra-terraform</span> │ │
|
||||
│ │ [ ] <span class="tui-bold">update-deps</span> │ │ │ Namespace: local/ │ │
|
||||
│ │ Phase: Idle │ │ │ Resources: 8 │ │
|
||||
│ │ Profile: auto │ │ │ Plans: 0 │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ └───────────────────────────────────────────┘ │ └──────────────────────────────────────────┘ │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-bold">PERSONA CYCLE LIST</span> (tab order) │
|
||||
│ ┌──────────────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 1. <span class="tui-primary">feature-dev</span> (claude-4-sonnet, 2 projects) │ │
|
||||
│ │ 2. <span class="tui-primary">reviewer</span> (gpt-4o, 1 project) │ │
|
||||
│ │ 3. <span class="tui-primary">infra-admin</span> (claude-4-opus, 1 project) │ │
|
||||
│ │ <span class="tui-dim">[+] Add current selection as persona...</span> │ │
|
||||
│ └──────────────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ Selected: 1 plan, 1 project │
|
||||
│ <span class="tui-dim">space</span> Select │ <span class="tui-dim">enter</span> Details │ <span class="tui-dim">ctrl+p</span> Create Persona │ <span class="tui-dim">/</span> Search │ <span class="tui-dim">d</span> Delete │ <span class="tui-dim">esc</span> Back │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
In fullscreen mode:
|
||||
|
||||
- **Selection mode**: `space` toggles selection on the highlighted plan or project. Selected items show `[x]`, deselected show `[ ]`.
|
||||
- **Persona creation**: `ctrl+p` opens the PersonaEditorModal with the selected plans and projects pre-populated as the scope.
|
||||
- **Detail inspection**: `enter` on a plan opens the PlanDetailModal; `enter` on a project opens the ProjectDetailModal.
|
||||
- **Persona cycle list**: The bottom panel shows the ordered list of personas that `tab` cycles through. Users can reorder (`ctrl+up`/`ctrl+down`), remove (`d`), or add new personas from the current selection.
|
||||
|
||||
|
||||
##### Sidebar State Transitions
|
||||
|
||||
| State | Layout | Input Focus | Content |
|
||||
|-------|--------|-------------|---------|
|
||||
| **Hidden** | Sidebar `display: none`; conversation full width | Prompt retains focus | No sidebar content visible |
|
||||
| **Visible** | Sidebar docked right, 32-40 chars wide | Prompt retains focus; `ctrl+b` focuses sidebar | Plans and Projects panels in collapsible containers |
|
||||
| **Fullscreen** | Covers entire screen | Sidebar takes input focus | Extended details, selection mode, persona management |
|
||||
|
||||
State transitions:
|
||||
```
|
||||
Hidden ──shift+tab──► Visible ──shift+tab──► Fullscreen
|
||||
▲ │
|
||||
└──────────────── escape (×1-2) ───────────────┘
|
||||
```
|
||||
|
||||
|
||||
#### Persona System
|
||||
|
||||
!!! adr "Architecture Decision"
|
||||
The persona abstraction, argument preset cycling, scope behavior, and persona lifecycle are defined in [ADR-045: TUI Persona System](adr/ADR-045-tui-persona-system.md).
|
||||
|
||||
A **persona** is a TUI-only abstraction that bundles:
|
||||
|
||||
1. **Actor reference** — a namespaced actor name (e.g., `anthropic/claude-4-sonnet`)
|
||||
2. **Base arguments** — default arguments passed to the actor on every invocation
|
||||
3. **Scoped projects** — projects always included in the session's context
|
||||
4. **Scoped plans** — plans always included in the session's context
|
||||
5. **Argument presets** — named argument overrides cycled with `ctrl+tab`
|
||||
6. **Display metadata** — short name, accent color, description
|
||||
|
||||
Personas are stored as YAML files in `~/.config/cleveragents/personas/` and are strictly a Presentation-layer concept — they never appear in the domain model, ACP protocol, or database schema.
|
||||
|
||||
|
||||
##### PersonaBar
|
||||
|
||||
The PersonaBar always shows the current state:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ <span class="tui-bold-primary">❯</span> What would you like to do? │
|
||||
│ <span class="tui-secondary">▌@▐</span> refs <span class="tui-secondary">▌/▐</span> commands <span class="tui-secondary">▌!▐</span> shell │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
<span class="tui-primary">feature-dev</span> │ <span class="tui-dim">claude-4-sonnet</span> │ <span class="tui-warning">think: high</span> │ <span class="tui-dim">2 projects</span> │ $0.12
|
||||
─────────── ─ ─────────────── ─ ────────── ─ ────────── ─ ────────────
|
||||
persona actor preset scope cost</pre></div>
|
||||
|
||||
| PersonaBar Segment | Style | Updates When |
|
||||
|--------------------|-------|--------------|
|
||||
| Persona name | `$text-primary` on `$primary 10%` bg | `tab` cycle |
|
||||
| Actor name | `$text-secondary` | `tab` cycle |
|
||||
| Preset label | `$text-warning` (non-default) / `$text-muted` (default) | `ctrl+tab` cycle |
|
||||
| Scope indicator | `$text-muted` | Persona change or `/scope:add/remove` |
|
||||
| Session cost | `$secondary 70%`, right-aligned | After each actor response |
|
||||
|
||||
|
||||
##### Persona Cycling
|
||||
|
||||
`tab` cycles through personas in the configured cycle list. `ctrl+tab` cycles through the current persona's argument presets:
|
||||
|
||||
```
|
||||
tab: persona_1 → persona_2 → persona_3 → persona_1 → ...
|
||||
ctrl+tab: default → think: high → think: max → quick → default → ...
|
||||
```
|
||||
|
||||
When cycling, the PersonaBar updates immediately. The actor binding for the session is updated — subsequent prompts use the new persona's actor and scope.
|
||||
|
||||
|
||||
##### First-Run Experience
|
||||
|
||||
On first launch (no personas configured), a centered overlay guides actor selection:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ <span class="tui-bold-primary">Welcome to CleverAgents</span> │
|
||||
│ │
|
||||
│ Select an actor to get started: │
|
||||
│ │
|
||||
│ <span class="tui-primary">❯</span> anthropic/claude-4-sonnet <span class="tui-dim">(recommended)</span> │
|
||||
│ anthropic/claude-4-opus │
|
||||
│ openai/gpt-4o │
|
||||
│ openai/o3 │
|
||||
│ google/gemini-2 │
|
||||
│ <span class="tui-dim">/ to search...</span> │
|
||||
│ │
|
||||
│ <span class="tui-dim">A default persona will be created with this actor.</span> │
|
||||
│ <span class="tui-dim">You can add more actors and personas later.</span> │
|
||||
│ │
|
||||
│──────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-dim">enter</span> Select │ <span class="tui-dim">j/k</span> Navigate │ <span class="tui-dim">/</span> Search │
|
||||
└──────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
Selection creates a `"default"` persona with the chosen actor and auto-generated argument presets (thinking effort levels are auto-detected from the actor's argument schema). Subsequent launches restore the last active persona.
|
||||
|
||||
|
||||
#### Reference and Command System
|
||||
|
||||
!!! adr "Architecture Decision"
|
||||
The @ reference notation grammar, fuzzy search algorithm, / command system, ! shell mode, and ACMS integration are defined in [ADR-046: TUI Reference and Command System](adr/ADR-046-tui-reference-and-command-system.md).
|
||||
|
||||
The TUI prompt supports three input modes, each activated by a distinct prefix:
|
||||
|
||||
| First Character | Mode | Prompt Symbol | Sent To | Overlay |
|
||||
|-----------------|------|---------------|---------|---------|
|
||||
| *(any other)* | Normal | `❯` | Actor via ACP | — |
|
||||
| `@` *(inline)* | Normal + Reference | `❯` | Actor (with CRP directives) | ReferencePickerOverlay |
|
||||
| `/` | Command | `/` | TUI command processor | SlashCommandOverlay |
|
||||
| `!` | Shell | `$` | Host OS subprocess | — |
|
||||
|
||||
|
||||
##### Reference Picker (@)
|
||||
|
||||
Typing `@` anywhere in a normal-mode prompt activates the Reference Picker — a fuzzy-search overlay for projects, plans, and resources:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌─ <span class="tui-bold-primary">Reference Picker</span> ───────────────────────────────────────────────────┐
|
||||
│ <span class="tui-bold">@hand</span> │
|
||||
│ ──────────────────────────────────────────────────────────────────── │
|
||||
│ <span class="tui-primary">PROJECT</span> api-service:src/auth/handler.py │
|
||||
│ local/api-service • Python • 245 lines │
|
||||
│ │
|
||||
│ <span class="tui-primary">PROJECT</span> cleveragents:git_dir/src/cli/commands/handler.py │
|
||||
│ local/cleveragents • Python • 189 lines │
|
||||
│ │
|
||||
│ <span class="tui-primary">PROJECT</span> api-service:src/auth/middleware/handler_base.py │
|
||||
│ local/api-service • Python • 67 lines │
|
||||
│ │
|
||||
│ <span class="tui-warning">PLAN</span> fix-auth-handler (01HXM8C2...) │
|
||||
│ Phase: Execute • Actor: claude-4-sonnet │
|
||||
│ │
|
||||
│ ──────────────────────────────────────────────────────────────────── │
|
||||
│ <span class="tui-dim">enter</span> Select │ <span class="tui-dim">tab</span> Tree │ <span class="tui-dim">ctrl+p</span> Projects │ <span class="tui-dim">ctrl+l</span> Plans │
|
||||
└──────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
Resolved references use a canonical notation: `@project:local/api-service:src/auth/handler.py`. Fuzzy input like `@handler.py` auto-expands to the canonical form on selection.
|
||||
|
||||
Resolved `@` references are translated into CRP (Context Request Protocol) directives before the prompt reaches the actor — they semantically direct the ACMS to prioritize referenced resources in context assembly.
|
||||
|
||||
|
||||
##### Reference Picker — Tree Browser Mode
|
||||
|
||||
Pressing `tab` in the Reference Picker switches to tree browser mode for hierarchical navigation:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌─ <span class="tui-bold-primary">Reference Picker (Tree)</span> ────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ▼ Projects │
|
||||
│ ▼ local/cleveragents │
|
||||
│ ▼ git_dir <span class="tui-dim">(git-checkout)</span> │
|
||||
│ ▸ src/ │
|
||||
│ ▸ tests/ │
|
||||
│ ▸ docs/ │
|
||||
│ ─ pyproject.toml │
|
||||
│ ─ README.md │
|
||||
│ ▸ database <span class="tui-dim">(sqlite-db)</span> │
|
||||
│ ▸ local/api-service │
|
||||
│ ▸ local/frontend-app │
|
||||
│ ▼ Plans │
|
||||
│ ▸ fix-auth-bug <span class="tui-dim">(01HXM8C2)</span> │
|
||||
│ ▸ refactor-models <span class="tui-dim">(01HXM9D3)</span> │
|
||||
│ │
|
||||
│ <span class="tui-dim">enter</span> Select │ <span class="tui-dim">tab</span> Search │ <span class="tui-dim">space</span> Expand │ <span class="tui-dim">/</span> Filter │
|
||||
└──────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
|
||||
##### Slash Command Overlay (/)
|
||||
|
||||
Typing `/` as the first character activates command mode. Commands are TUI operations executed locally — they are **not** sent to the actor.
|
||||
|
||||
|
||||
###### Slash Command Overlay
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌─ <span class="tui-bold-primary">Commands</span> ─────────────────────────────────────────────────────────────┐
|
||||
│ <span class="tui-bold">/se</span> │
|
||||
│ ───────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-primary">/session:create</span> Create a new session tab │
|
||||
│ <span class="tui-primary">/session:list</span> Show all sessions │
|
||||
│ <span class="tui-primary">/session:show</span> Show session details │
|
||||
│ <span class="tui-primary">/session:switch</span> Switch to session by ID │
|
||||
│ <span class="tui-primary">/session:close</span> Close current session │
|
||||
│ <span class="tui-primary">/session:delete</span> Delete a saved session │
|
||||
│ <span class="tui-primary">/session:rename</span> Rename current session │
|
||||
│ <span class="tui-primary">/session:export</span> Export session to file │
|
||||
│ <span class="tui-primary">/session:import</span> Import session from file │
|
||||
│ <span class="tui-primary">/settings</span> Open settings screen │
|
||||
│ │
|
||||
│ <span class="tui-dim">enter</span> Execute │ <span class="tui-dim">tab</span> Complete │ <span class="tui-dim">escape</span> Dismiss │
|
||||
└────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
TUI slash commands mirror CLI command patterns where applicable. The CLI uses `agents <noun> <verb>`; the TUI uses `/<noun>:<verb>`. Commands that exist in both CLI and TUI use the same verb names. TUI-only commands (persona, scope, TUI utilities) have no CLI equivalent.
|
||||
|
||||
|
||||
###### Complete Command Reference
|
||||
|
||||
**Session Commands** — *mirrors CLI `agents session <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/session:create` | `[--persona <name>]` | Create a new session tab |
|
||||
| `/session:list` | — | Display all sessions |
|
||||
| `/session:show <id>` | `<id>` | Show session details |
|
||||
| `/session:switch <id>` | `<id>` | Switch to session by ID or tab index |
|
||||
| `/session:close` | `[--force]` | Close the current session tab |
|
||||
| `/session:delete <id>` | `<id>`, `[--yes/-y]` | Delete a saved session |
|
||||
| `/session:rename <name>` | `<name>` | Rename the current session |
|
||||
| `/session:export [path]` | `[path]` | Export session to JSON |
|
||||
| `/session:import <path>` | `<path>` | Import session from JSON |
|
||||
|
||||
**Persona Commands** — *TUI-only*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/persona:list` | — | Display all personas |
|
||||
| `/persona:set <name>` | `<name>` | Switch to persona |
|
||||
| `/persona:create` | — | Open PersonaEditorModal |
|
||||
| `/persona:edit [name]` | `[name]` | Edit persona |
|
||||
| `/persona:delete <name>` | `<name>` | Delete persona |
|
||||
| `/persona:export <name>` | `<name>` | Export persona YAML |
|
||||
| `/persona:import <path>` | `<path>` | Import persona YAML |
|
||||
|
||||
**Scope Commands** — *TUI-only*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/scope:add <ref>` | `<ref>` | Add project/plan to session scope |
|
||||
| `/scope:remove <ref>` | `<ref>` | Remove from session scope |
|
||||
| `/scope:clear` | — | Clear session-level scope additions |
|
||||
| `/scope:show` | — | Show effective scope |
|
||||
|
||||
**Plan Commands** — *mirrors CLI `agents plan <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/plan:use <action>` | `<action>`, `[projects...]`, `[--arg/-a <key=value>]` | Start a new plan |
|
||||
| `/plan:list` | `[--phase]` `[--state]` `[--project]` `[--action]` | List plans |
|
||||
| `/plan:status [id]` | `[id]` | Show plan status |
|
||||
| `/plan:tree <id>` | `<id>`, `[--show-superseded]` `[--depth]` | Show decision tree |
|
||||
| `/plan:execute [id]` | `[id]` | Execute a plan |
|
||||
| `/plan:apply [id]` | `[id]` | Apply a completed plan |
|
||||
| `/plan:cancel <id>` | `<id>`, `[--reason/-r]` | Cancel a plan |
|
||||
| `/plan:diff <id>` | `<id>`, `[--correction]` | Show plan diff |
|
||||
| `/plan:correct <did>` | `<did>`, `--mode`, `--guidance` | Correct a decision |
|
||||
| `/plan:resume <id>` | `<id>`, `[--dry-run]` | Resume a plan |
|
||||
| `/plan:revert <id>` | `<id>`, `[--to-phase]` | Revert a plan |
|
||||
| `/plan:rollback <id> <cp>` | both required | Rollback to checkpoint |
|
||||
| `/plan:explain <did>` | `<did>`, `[--show-context]` | Explain a decision |
|
||||
| `/plan:errors <id>` | `<id>` | Show plan errors |
|
||||
| `/plan:artifacts <id>` | `<id>` | Show plan artifacts |
|
||||
| `/plan:inspect [id]` | `[id]` | Open PlanDetailModal |
|
||||
|
||||
**Project Commands** — *mirrors CLI `agents project <verb>`*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/project:list` | `[--namespace/-n]` | List all projects |
|
||||
| `/project:create <name>` | `<name>`, `[--description/-d]` `[--resource/-r]` | Create project |
|
||||
| `/project:show <name>` | `<name>` | Show project details |
|
||||
| `/project:delete <name>` | `<name>`, `[--yes/-y]` | Delete project |
|
||||
| `/project:inspect <name>` | `<name>` | Open ProjectDetailModal |
|
||||
| `/project:context:show <name>` | `<name>` | Show project context config |
|
||||
|
||||
**Actor, Resource, Config, Tool, Skill, Invariant, Profile Commands** — *mirrors CLI*
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `/actor:list`, `/actor:show`, `/actor:set-default` | Actor management |
|
||||
| `/resource:list`, `/resource:show`, `/resource:tree`, `/resource:inspect` | Resource management |
|
||||
| `/config:list`, `/config:get`, `/config:set` | Configuration management |
|
||||
| `/tool:list`, `/tool:show` | Tool management |
|
||||
| `/skill:list`, `/skill:show` | Skill management |
|
||||
| `/invariant:list`, `/invariant:add`, `/invariant:remove` | Invariant management |
|
||||
| `/profile:list`, `/profile:show` | Automation profile management |
|
||||
|
||||
**TUI Utility Commands** — *TUI-only*
|
||||
|
||||
| Command | Arguments | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `/clear` | — | Clear conversation display |
|
||||
| `/theme [name]` | `[name]` | Switch color theme |
|
||||
| `/settings` | — | Open SettingsScreen |
|
||||
| `/help [command]` | `[command]` | Show help |
|
||||
| `/about` | — | Show version and system info |
|
||||
| `/debug` | — | Toggle debug mode |
|
||||
|
||||
|
||||
##### Shell Mode (!)
|
||||
|
||||
Shell mode provides direct OS command execution from the TUI prompt. Typing `!` as the first character changes the prompt symbol to `$` and enables shell syntax highlighting, file/directory tab completion, and separate shell history.
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌────────────────────────────────────────────────────────────────────┐
|
||||
│ <span class="tui-bold">$</span> git status │
|
||||
│ ┌────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ On branch main │ │
|
||||
│ │ Your branch is up to date with 'origin/main'. │ │
|
||||
│ │ │ │
|
||||
│ │ Changes not staged for commit: │ │
|
||||
│ │ modified: src/auth/handler.py │ │
|
||||
│ │ modified: src/auth/middleware.py │ │
|
||||
│ └────────────────────────────────────────────────────────────────┘ │
|
||||
│ <span class="tui-dim">(exit code: 0)</span> │
|
||||
└────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
Shell results appear as `ShellResult` blocks in the conversation stream with a `$primary` left border and `$foreground 4%` background. Long-running commands stream output in real-time; `ctrl+c` sends SIGINT.
|
||||
|
||||
|
||||
#### Plan Detail Modal
|
||||
|
||||
The PlanDetailModal shows a plan's decision tree alongside decision details:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌───────────────────────────────────── <span class="tui-bold">Plan: fix-auth-bug</span> ─────────────────────────────────────┐
|
||||
│ ID: <span class="tui-dim">01HXM8C2ABCD</span> Phase: <span class="tui-primary">Execute</span> State: <span class="tui-primary">in_progress</span> │
|
||||
│ Profile: trusted Actor: claude-4-sonnet Started: 2m ago Cost: $0.08 │
|
||||
│ Projects: local/api-service │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-bold">DECISION TREE</span> │ <span class="tui-bold">DECISION DETAIL</span> │
|
||||
│ ┌──────────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │
|
||||
│ │ │ │ │ Decision: <span class="tui-bold">D3</span> │ │
|
||||
│ │ ▼ D1 strategy_choice <span class="tui-success">✔</span> │ │ │ Type: implementation_choice │ │
|
||||
│ │ ├─ D2 invariant_enforced <span class="tui-success">✔</span> │ │ │ Phase: Execute │ │
|
||||
│ │ ├─▶<span class="tui-bold">D3 implementation_choice</span> <span class="tui-warning">⚡</span> │ │ │ │ │
|
||||
│ │ │ ├─ D4 tool_invocation <span class="tui-success">✔</span> │ │ │ Question: │ │
|
||||
│ │ │ ├─ D5 tool_invocation <span class="tui-dim">⌛</span> │ │ │ How should rate limiting be │ │
|
||||
│ │ │ └─ D6 tool_invocation <span class="tui-dim">⌛</span> │ │ │ implemented on the login endpoint? │ │
|
||||
│ │ ├─ D7 resource_selection <span class="tui-dim">⌛</span> │ │ │ │ │
|
||||
│ │ └─ D8 subplan_spawn <span class="tui-dim">⌛</span> │ │ │ Chosen: <span class="tui-bold">Token bucket algorithm</span> │ │
|
||||
│ │ │ │ │ with Redis backing store │ │
|
||||
│ │ <span class="tui-dim">/ Search decisions...</span> │ │ │ │ │
|
||||
│ │ │ │ │ Alternatives: │ │
|
||||
│ └──────────────────────────────────────┘ │ │ <span class="tui-dim">• Sliding window counter</span> │ │
|
||||
│ │ │ <span class="tui-dim">• Fixed window counter</span> │ │
|
||||
│ Legend: │ │ <span class="tui-dim">• IP-based blocking</span> │ │
|
||||
│ <span class="tui-success">✔</span> completed <span class="tui-warning">⚡</span> active │ │ │ │
|
||||
│ <span class="tui-dim">⌛</span> pending <span class="tui-error">✗</span> failed │ │ Confidence: <span class="tui-bold">0.85</span> │ │
|
||||
│ <span class="tui-dim">⊘</span> superseded │ │ Rationale: Token bucket provides │ │
|
||||
│ │ │ fine-grained control with burst │ │
|
||||
│ ─────────────────────────────────────── │ │ allowance and Redis enables │ │
|
||||
│ Phase: <span class="tui-success">●●●</span><span class="tui-dim">○</span> │ │ distributed rate limiting... │ │
|
||||
│ Decisions: 8 total │ │ │ │
|
||||
│ 4 completed, 1 active, 3 pending │ │ Dependencies: │ │
|
||||
│ │ │ ← D1 (strategy_choice) │ │
|
||||
│ │ │ → D4, D5, D6 (tool_invocations) │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ Context Snapshot: 2.3 KB │ │
|
||||
│ │ └────────────────────────────────────────┘ │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-dim">j/k</span> Nav │ <span class="tui-dim">enter</span> Inspect │ <span class="tui-dim">c</span> Correct │ <span class="tui-dim">d</span> Diff │ <span class="tui-dim">h</span> History │ <span class="tui-dim">t</span> Toggle │ <span class="tui-dim">/</span> Search │
|
||||
│ <span class="tui-dim">s</span> Superseded │ <span class="tui-dim">x</span> Expand all │ <span class="tui-dim">z</span> Collapse all │ <span class="tui-dim">escape</span> Close │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
|
||||
##### Decision Tree Interaction
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `j` / `down` | Move to next decision |
|
||||
| `k` / `up` | Move to previous decision |
|
||||
| `enter` | Select decision → show detail in right panel |
|
||||
| `space` | Expand/collapse subtree |
|
||||
| `c` | Correct selected decision (opens correction dialog) |
|
||||
| `d` | Show diff for selected decision |
|
||||
| `h` | Show decision history / superseded chain |
|
||||
| `t` | Toggle detail panel visibility |
|
||||
| `x` | Expand all nodes |
|
||||
| `z` | Collapse all nodes |
|
||||
| `s` | Show superseded decisions (hidden by default) |
|
||||
| `/` | Search decisions by type, content, or ID |
|
||||
| `escape` | Close modal |
|
||||
|
||||
|
||||
#### Project Detail Modal
|
||||
|
||||
The ProjectDetailModal shows a project's resource DAG, invariants, and running plans:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌───────────────────────────────── <span class="tui-bold">Project: local/api-service</span> ─────────────────────────────────┐
|
||||
│ Namespace: local/ Resources: 3 Plans: 1 active │
|
||||
│ Invariants: 1 Validations: 4 Automation: trusted │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-bold">RESOURCES</span> │ <span class="tui-bold">DETAIL</span> │
|
||||
│ ┌──────────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │
|
||||
│ │ │ │ │ Resource: <span class="tui-bold">api-repo</span> │ │
|
||||
│ │ ▼ api-repo <span class="tui-dim">(git-checkout)</span> │ │ │ Type: git-checkout │ │
|
||||
│ │ ├─ src/ <span class="tui-dim">(fs-directory)</span> │ │ │ ID: <span class="tui-dim">01HXYZ...</span> │ │
|
||||
│ │ │ ├─ auth/ │ │ │ │ │
|
||||
│ │ │ │ ├─ handler.py │ │ │ URL: git@github.com:org/api.git │ │
|
||||
│ │ │ │ ├─ middleware.py │ │ │ Branch: main │ │
|
||||
│ │ │ │ └─ limiter.py │ │ │ Commit: <span class="tui-dim">a1b2c3d (2h ago)</span> │ │
|
||||
│ │ │ ├─ models/ │ │ │ │ │
|
||||
│ │ │ └─ routes/ │ │ │ Sandbox: git_worktree │ │
|
||||
│ │ ├─ tests/ <span class="tui-dim">(fs-directory)</span> │ │ │ Checkpoint: enabled │ │
|
||||
│ │ └─ pyproject.toml │ │ │ │ │
|
||||
│ │ ─ api-db <span class="tui-dim">(container-instance)</span> │ │ │ Children: 2 (src/, tests/) │ │
|
||||
│ │ ─ api-cache <span class="tui-dim">(container-instance)</span> │ │ │ Validations: 2 attached │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ └──────────────────────────────────────┘ │ └────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ <span class="tui-bold">INVARIANTS</span> │ <span class="tui-bold">RUNNING PLANS</span> │
|
||||
│ ┌──────────────────────────────────────┐ │ ┌────────────────────────────────────────┐ │
|
||||
│ │ 1. All API endpoints must have │ │ │ fix-auth-bug Execute <span class="tui-success">●●●</span><span class="tui-dim">○</span> $0.08 │ │
|
||||
│ │ rate limiting (project-level) │ │ │ │ │
|
||||
│ └──────────────────────────────────────┘ │ └────────────────────────────────────────┘ │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-dim">j/k</span> Nav │ <span class="tui-dim">enter</span> Drill │ <span class="tui-dim">i</span> Invariants │ <span class="tui-dim">v</span> Validations │ <span class="tui-dim">p</span> Plans │ <span class="tui-dim">/</span> Search │ <span class="tui-dim">esc</span> Close │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
|
||||
#### Persona Editor Modal
|
||||
|
||||
The PersonaEditorModal is used for creating and editing personas:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌─────────────────────────────────────── <span class="tui-bold">Create Persona</span> ───────────────────────────────────────┐
|
||||
│ │
|
||||
│ Name: ┌──────────────────────────────────────┐ │
|
||||
│ │ feature-dev │ │
|
||||
│ └──────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Description: ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ Feature development with Claude on main projects │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Actor: ┌──────────────────────────────────────────┐ │
|
||||
│ │ <span class="tui-primary">❯ anthropic/claude-4-sonnet</span> │ ▼ │
|
||||
│ └──────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Color: <span class="tui-primary">● $primary</span> <span class="tui-dim">○ $secondary ○ $success ○ $warning ○ $error</span> │
|
||||
│ │
|
||||
│ ── <span class="tui-bold">Base Arguments</span> (from actor schema) ────────────────────────────────────── │
|
||||
│ thinking_effort: ┌──────────┐ temperature: ┌──────────┐ │
|
||||
│ │ medium │ ▼ │ 0.7 │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ max_tokens: ┌──────────┐ │
|
||||
│ │ 16384 │ │
|
||||
│ └──────────┘ │
|
||||
│ │
|
||||
│ ── <span class="tui-bold">Scoped Projects</span> ───────────────────────────────────────────────────────── │
|
||||
│ [x] local/cleveragents [x] local/api-service │
|
||||
│ [ ] local/frontend-app [ ] local/infra-terraform │
|
||||
│ │
|
||||
│ ── <span class="tui-bold">Scoped Plans</span> ──────────────────────────────────────────────────────────── │
|
||||
│ [ ] fix-auth-bug (01HXM8C2) [ ] refactor-models (01HXM9D3) │
|
||||
│ │
|
||||
│ ── <span class="tui-bold">Argument Presets</span> (ctrl+tab cycling) ───────────────────────────────────── │
|
||||
│ ┌────────────────────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 1. default │ <span class="tui-dim">(base arguments)</span> │ │
|
||||
│ │ 2. think: high │ thinking_effort=high │ │
|
||||
│ │ 3. think: max │ thinking_effort=max, temperature=0.3 │ │
|
||||
│ │ 4. quick │ thinking_effort=low, max_tokens=4096 │ │
|
||||
│ │ <span class="tui-dim">[+] Add preset</span> │ │
|
||||
│ └────────────────────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Cycle Order: ┌──────┐ <span class="tui-dim">(0 = not in tab cycle)</span> │
|
||||
│ │ 1 │ │
|
||||
│ └──────┘ │
|
||||
│ │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-dim">tab</span> Next field │ <span class="tui-dim">enter</span> Save │ <span class="tui-dim">escape</span> Cancel │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
|
||||
#### Permissions Screen
|
||||
|
||||
When a tool requests permission to modify resources, the PermissionsScreen shows the requested changes with a diff view:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌───────────────────────────────────── <span class="tui-bold">Permission Request</span> ─────────────────────────────────────┐
|
||||
│ │
|
||||
│ local/file-write wants to modify 3 files in local/api-service: │
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │Files (3 changes): │ │<span class="tui-bold">src/auth/handler.py</span> │ │
|
||||
│ │ │ │──────────────────────────────────────────────────────│ │
|
||||
│ │<span class="tui-primary">❯ src/auth/handler.py</span> [M] │ │ @@ -45,6 +45,12 @@ │ │
|
||||
│ │ src/auth/middleware.py [M] │ │ def login(self, request): │ │
|
||||
│ │ src/auth/limiter.py [A] │ │ <span class="tui-success">+ @rate_limit(max_calls=5, period=60)</span> │ │
|
||||
│ │ │ │ credentials = self.extract(request) │ │
|
||||
│ │ │ │ <span class="tui-error">- return self.authenticate(credentials)</span> │ │
|
||||
│ │ │ │ <span class="tui-success">+ result = self.authenticate(credentials)</span> │ │
|
||||
│ │ │ │ <span class="tui-success">+ self.log_attempt(request, result)</span> │ │
|
||||
│ │ │ │ <span class="tui-success">+ return result</span> │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ @@ -78,3 +84,8 @@ │ │
|
||||
│ │ │ │ def logout(self, request): │ │
|
||||
│ │ │ │ <span class="tui-success">+ self.cleanup_session(request)</span> │ │
|
||||
│ │ │ │ return Response(status=200) │ │
|
||||
│ │ │ │ │ │
|
||||
│ └─────────────────────────────┘ └──────────────────────────────────────────────────────┘ │
|
||||
│──────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ <span class="tui-warning">[M]</span> Modified <span class="tui-success">[A]</span> Added <span class="tui-error">[D]</span> Deleted │
|
||||
│ <span class="tui-dim">a</span> Allow │ <span class="tui-dim">A</span> Allow Always │ <span class="tui-dim">r</span> Reject │ <span class="tui-dim">R</span> Reject Always │ <span class="tui-dim">j/k</span> Nav │ <span class="tui-dim">d</span> Diff │ <span class="tui-dim">esc</span> │
|
||||
└──────────────────────────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
The diff view supports three display modes, toggled with `d`:
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| **Unified** | Standard unified diff with +/- lines (default) |
|
||||
| **Side-by-side** | Two-column view with old content left, new content right |
|
||||
| **Context** | Shows only changed lines with surrounding context (3 lines default) |
|
||||
|
||||
|
||||
#### Additional UI Components
|
||||
|
||||
|
||||
##### Throbber
|
||||
|
||||
A rainbow gradient animated bar spans the full width at the top, visible only when the actor is processing:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌───────────────────────────────────────────────────────────────────────────┐
|
||||
│<span class="tui-rainbow"> ════════════════════════════════════ ◆ ═══════════════════════════════════</span>│
|
||||
│ <span class="tui-dim">(actor processing...)</span> │
|
||||
│ │
|
||||
│ <span class="tui-dim">(idle — throbber collapses to zero height)</span> │
|
||||
└───────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
The gradient cycles through 12 colors at 15fps:
|
||||
`#881177` → `#aa3355` → `#cc6666` → `#ee9944` → `#eedd00` → `#99dd55` → `#44dd88` → `#22ccbb` → `#00bbcc` → `#0099cc` → `#3366bb` → `#663399`
|
||||
|
||||
|
||||
##### Session Tabs
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌────────────────────────────────────────────────────────────────────────────┐
|
||||
│ <span class="tui-dim">Session 1</span> ┃ <span class="tui-primary-u">Session 2</span> ┃ <span class="tui-dim">Session 3</span> ┃ <span class="tui-dim">⌛ Session 4</span> │
|
||||
│ ┗━━━━━━━━━━━┛ │
|
||||
│ │
|
||||
│ <span class="tui-dim">ctrl+[</span> Previous │ <span class="tui-dim">ctrl+]</span> Next │ <span class="tui-dim">ctrl+n</span> New │ <span class="tui-dim">ctrl+w</span> Close │
|
||||
│ <span class="tui-dim">ctrl+s</span> Sessions screen │ <span class="tui-dim">1-9</span> Jump to tab │
|
||||
│ │
|
||||
│ Indicators: <span class="tui-dim">⌛</span> actor working │ <span class="tui-primary">❯</span> awaiting input │ <span class="tui-dim">(plain)</span> idle │
|
||||
│ <span class="tui-primary-u">━━━</span> active tab underline │
|
||||
└────────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
|
||||
##### Notifications (Flash)
|
||||
|
||||
Notifications appear as a single-line Flash bar above the prompt, auto-dismissing after a configurable timeout (default 5s):
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌────────────────────────────────────────┐
|
||||
│ <span class="tui-success">✔ Plan fix-auth-bug completed</span> │
|
||||
│ <span class="tui-dim">(auto-dismiss in 5s)</span> │
|
||||
│ │
|
||||
│ <span class="tui-error">✗ Plan refactor-models failed</span> │
|
||||
└────────────────────────────────────────┘</pre></div>
|
||||
|
||||
|
||||
##### Help Panel (F1)
|
||||
|
||||
`F1` opens a context-sensitive help overlay showing available hotkeys for the current screen/widget:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌─ <span class="tui-bold-primary">Help: Main Screen</span> ─────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ <span class="tui-bold">Navigation</span> │
|
||||
│ <span class="tui-dim">shift+tab</span> Cycle sidebar: hidden → visible → fullscreen │
|
||||
│ <span class="tui-dim">tab</span> Cycle to next persona │
|
||||
│ <span class="tui-dim">ctrl+tab</span> Cycle to next argument preset │
|
||||
│ <span class="tui-dim">ctrl+[/]</span> Switch session tab │
|
||||
│ <span class="tui-dim">escape</span> Close overlay / sidebar / return to prompt │
|
||||
│ │
|
||||
│ <span class="tui-bold">Prompt</span> │
|
||||
└─────────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
|
||||
##### Escape Cascade
|
||||
|
||||
The escape key always cascades toward the main screen:
|
||||
|
||||
|
||||
<div class="tui-mockup"><pre>┌───────────────────────────────────────────────────────────────────────┐
|
||||
│ Modal (PlanDetail, ProjectDetail, PersonaEditor, ...) │
|
||||
│ <span class="tui-dim">↓ escape</span> │
|
||||
│ Overlay (ReferencePicker, SlashCommand) │
|
||||
│ <span class="tui-dim">↓ escape</span> │
|
||||
│ Sidebar Fullscreen │
|
||||
│ <span class="tui-dim">↓ escape</span> │
|
||||
│ Sidebar Visible │
|
||||
└───────────────────────────────────────────────────────────────────────┘</pre></div>
|
||||
|
||||
Multiple escape presses from any state eventually reach the main chat prompt. No modal or screen may trap the user without an escape path.
|
||||
|
||||
|
||||
#### Conversation Stream
|
||||
|
||||
The conversation displays a chronological stream of typed message blocks:
|
||||
|
||||
| Block Type | Visual Treatment | Source |
|
||||
|------------|-----------------|--------|
|
||||
| `Welcome` | ASCII art + instructions in `$text-success` | App startup (first message) |
|
||||
| `UserInput` | Left border `$secondary`, 15% background tint, Markdown | User prompt submission |
|
||||
| `ActorResponse` | Streaming Markdown with syntax-highlighted code fences | `session.message` events |
|
||||
| `ActorThought` | `$primary-muted` 20% bg, max 10 lines (expandable), italic | Actor reasoning |
|
||||
| `ToolCall` | Expandable: icon + status pill header, collapsible content | `tool.invoked` / `tool.completed` |
|
||||
| `PlanProgress` | Grid layout with status icons per step | Plan phase changes |
|
||||
| `DiffView` | Unified or side-by-side diff, syntax highlighting | Tool results with diffs |
|
||||
| `TerminalEmbed` | Bordered terminal, `$primary 50%` border, green/red tint | Shell or tool terminal output |
|
||||
| `ShellResult` | Left border `$primary`, 4% foreground bg | User shell command (!) output |
|
||||
| `Note` | Semantic: info (`$primary`), warning (`$warning`), error (`$error`) | System notifications |
|
||||
|
||||
The conversation uses a 2-column grid: a 1-character cursor column (left, navigable via `alt+up`/`alt+down`) and the content stream (right). The block cursor provides keyboard-driven message navigation — `enter` or `space` expands/collapses a block.
|
||||
|
||||
|
||||
#### Theme and Styling
|
||||
|
||||
The TUI uses Textual's CSS (TCSS) system with semantic color tokens:
|
||||
|
||||
| Token | Usage |
|
||||
|-------|-------|
|
||||
| `$primary` | Active elements, sidebar borders, plan status |
|
||||
| `$secondary` | User input highlights, prompt accent |
|
||||
| `$success` | Completed states, passed validations |
|
||||
| `$error` | Failed states, error notifications |
|
||||
| `$warning` | Caution states, cost warnings |
|
||||
| `$text`, `$text-muted` | Primary and secondary text |
|
||||
|
||||
Default theme is **Dracula**. All 17+ Textual built-in themes are supported and switchable via `/theme <name>`. The TCSS file hierarchy follows the screen/widget module structure.
|
||||
|
||||
|
||||
#### Global Hotkey Reference
|
||||
|
||||
| Key | Context | Action |
|
||||
|-----|---------|--------|
|
||||
| `shift+tab` | Anywhere | Cycle sidebar: hidden → visible → fullscreen |
|
||||
| `tab` | Prompt | Cycle to next persona |
|
||||
| `ctrl+tab` | Prompt | Cycle to next argument preset |
|
||||
| `ctrl+[` / `ctrl+]` | Anywhere | Switch session tabs |
|
||||
| `ctrl+n` | Anywhere | Create new session tab |
|
||||
| `ctrl+w` | Anywhere | Close current session tab |
|
||||
| `ctrl+s` | Anywhere | Open Sessions screen |
|
||||
| `ctrl+b` | Main screen | Focus sidebar (when visible) |
|
||||
| `ctrl+q` | Anywhere | Quit TUI |
|
||||
| `F1` | Anywhere | Toggle help panel |
|
||||
| `F2` / `ctrl+,` | Anywhere | Open settings |
|
||||
| `escape` | Anywhere | Close current overlay/modal/sidebar toward main screen |
|
||||
| `alt+up` / `alt+down` | Conversation | Navigate message blocks |
|
||||
| `enter` / `space` | Conversation (block focused) | Expand/collapse block |
|
||||
|
||||
## Configuration
|
||||
|
||||
!!! adr "Architecture Decision"
|
||||
|
||||
@@ -560,6 +560,83 @@ img[alt="Kroki"]:hover {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* ── TUI Mockup Diagrams ─────────────────────────────────────────
|
||||
*
|
||||
* Renders TUI specification ASCII mockups as styled terminal windows
|
||||
* with Dracula-inspired coloring. Span classes map to the TUI's
|
||||
* semantic color tokens ($primary, $secondary, $success, etc.).
|
||||
*
|
||||
* Usage in markdown:
|
||||
* <div class="tui-mockup"><pre>
|
||||
* ┌───────────┐
|
||||
* │ <span class="tui-primary">hello</span> │
|
||||
* └───────────┘</pre></div>
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
.tui-mockup {
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.tui-mockup pre {
|
||||
/* Dracula palette */
|
||||
--tui-fg: #f8f8f2;
|
||||
--tui-bg: #21222c;
|
||||
--tui-primary: #8be9fd;
|
||||
--tui-secondary: #bd93f9;
|
||||
--tui-success: #50fa7b;
|
||||
--tui-warning: #f1fa8c;
|
||||
--tui-error: #ff5555;
|
||||
--tui-dim: #6272a4;
|
||||
--tui-border-color: #44475a;
|
||||
|
||||
background: var(--tui-bg) !important;
|
||||
color: var(--tui-fg) !important;
|
||||
font-family: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', 'Consolas', 'Monaco', monospace;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.45;
|
||||
padding: 0.8rem 1rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--tui-border-color);
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Semantic color spans */
|
||||
.tui-mockup .tui-primary { color: var(--tui-primary); }
|
||||
.tui-mockup .tui-secondary { color: var(--tui-secondary); }
|
||||
.tui-mockup .tui-success { color: var(--tui-success); }
|
||||
.tui-mockup .tui-warning { color: var(--tui-warning); }
|
||||
.tui-mockup .tui-error { color: var(--tui-error); }
|
||||
.tui-mockup .tui-dim { color: var(--tui-dim); }
|
||||
.tui-mockup .tui-bold { font-weight: 700; }
|
||||
.tui-mockup .tui-bold-primary { font-weight: 700; color: var(--tui-primary); }
|
||||
.tui-mockup .tui-bold-warning { font-weight: 700; color: var(--tui-warning); }
|
||||
.tui-mockup .tui-primary-u { color: var(--tui-primary); text-decoration: underline; text-underline-offset: 2px; }
|
||||
|
||||
/* Rainbow gradient text (throbber bar). Uses the exact gradient
|
||||
* colors from the TUI specification's throbber section, animated
|
||||
* to cycle continuously. */
|
||||
.tui-mockup .tui-rainbow {
|
||||
font-weight: 700;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#881177, #aa3355, #cc6666, #ee9944, #eedd00,
|
||||
#99dd55, #44dd88, #22ccbb, #00bbcc, #0099cc,
|
||||
#3366bb, #663399, #881177
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
background-size: 200% 100%;
|
||||
animation: tui-rainbow-shift 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes tui-rainbow-shift {
|
||||
0% { background-position: 0% 50%; }
|
||||
100% { background-position: 200% 50%; }
|
||||
}
|
||||
|
||||
/* ── ADR Admonition ──────────────────────────────────────────────── */
|
||||
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user