Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ backend/MagicMock/
bandit-out.json

# Release assets and generated agent state
.gemini/
backend/.grinta/
rustup-init.exe
grinta_raft.mp4
Expand Down
52 changes: 40 additions & 12 deletions backend/cli/tui/dialogs/hud_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ class GrintaHUDControlsDialog(ModalDialog[dict[str, str] | None]):
GrintaHUDControlsDialog Select { margin-bottom: 1; }
"""

def __init__(self, *, mode: str, autonomy: str, reasoning: str,
mode_options: list[tuple[str, str]],
autonomy_options: list[tuple[str, str]],
reasoning_options: list[tuple[str, str]]) -> None:
def __init__(
self,
*,
mode: str,
autonomy: str,
reasoning: str,
mode_options: list[tuple[str, str]],
autonomy_options: list[tuple[str, str]],
reasoning_options: list[tuple[str, str]],
) -> None:
super().__init__()
self._mode = mode
self._autonomy = autonomy
Expand All @@ -34,16 +40,32 @@ def __init__(self, *, mode: str, autonomy: str, reasoning: str,

self._autonomy_options = autonomy_options
self._reasoning_options = reasoning_options or [('Default', '')]

def compose(self) -> ComposeResult:
with Vertical(id='dialog-container'):
yield Label('Session controls', id='dialog-title')
yield Static('Controls normally shown in the HUD.', id='dialog-subtitle')
yield Label('Mode', classes='field-label')
yield Select(self._mode_options, value=self._mode, allow_blank=False, id='hud-drawer-mode')
yield Select(
self._mode_options,
value=self._mode,
allow_blank=False,
id='hud-drawer-mode',
)
yield Label('Autonomy', classes='field-label')
yield Select(self._autonomy_options, value=self._autonomy, allow_blank=False, id='hud-drawer-autonomy')
yield Select(
self._autonomy_options,
value=self._autonomy,
allow_blank=False,
id='hud-drawer-autonomy',
)
yield Label('Reasoning', classes='field-label')
yield Select(self._reasoning_options, value=self._reasoning, allow_blank=False, id='hud-drawer-reasoning')
yield Select(
self._reasoning_options,
value=self._reasoning,
allow_blank=False,
id='hud-drawer-reasoning',
)
with Horizontal(id='dialog-buttons'):
yield Button('Apply', id='hud-drawer-apply', variant='primary')
yield Button('Cancel', id='hud-drawer-cancel')
Expand All @@ -55,8 +77,14 @@ def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == 'hud-drawer-cancel':
self.dismiss(None)
elif event.button.id == 'hud-drawer-apply':
self.dismiss({
'mode': str(self.query_one('#hud-drawer-mode', Select).value),
'autonomy': str(self.query_one('#hud-drawer-autonomy', Select).value),
'reasoning': str(self.query_one('#hud-drawer-reasoning', Select).value),
})
self.dismiss(
{
'mode': str(self.query_one('#hud-drawer-mode', Select).value),
'autonomy': str(
self.query_one('#hud-drawer-autonomy', Select).value
),
'reasoning': str(
self.query_one('#hud-drawer-reasoning', Select).value
),
}
)
3 changes: 0 additions & 3 deletions backend/cli/tui/renderer/handlers/delegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,3 @@ def _handle_delegate_task_observation(
preview,
getattr(event, 'cause', None),
)
from backend.cli.tui.widgets.activity_card import ToolResult

orch._append_transcript_widget(ToolResult('Delegate', detail, success=success))
2 changes: 1 addition & 1 deletion backend/cli/tui/renderer/handlers/file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""File read / edit event handlers for the TUI renderer.

Mounts :class:`EditCard` (1-line scan row with ⤢ detail) per edit.
Mounts :class:`EditCard` with an inline diff and overflow detail per edit.
Supports multiedit splitting — one card per entry in
``FileEditAction.structured_payload.file_edits[]``.
"""
Expand Down
7 changes: 0 additions & 7 deletions backend/cli/tui/renderer/handlers/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def _handle_mcp_observation(
pending = orch._take_tool_card(action_id, expected_kind='mcp')
if pending is None and (action_id is None or action_id < 0):
pending = orch._pending_mcp_card
from backend.cli.tui.widgets.activity_card import ToolResult
from backend.cli.tui.widgets.scan_line import MCPCard

if isinstance(pending, MCPCard):
Expand All @@ -77,9 +76,6 @@ def _handle_mcp_observation(
if orch._pending_mcp_card is pending:
orch._pending_mcp_card = None
orch._pending_exploration_meta = None
orch._append_transcript_widget(
ToolResult(event.name, content, success=not is_error)
)
return

orch._append_scan_line_card(
Expand All @@ -94,6 +90,3 @@ def _handle_mcp_observation(
if action_id is None or action_id < 0:
orch._pending_mcp_card = None
orch._pending_exploration_meta = None
orch._append_transcript_widget(
ToolResult(event.name, content, success=not is_error)
)
5 changes: 1 addition & 4 deletions backend/cli/tui/renderer/handlers/shell.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
"""Shell / CmdRun event handlers for the TUI renderer.

Mounts :class:`ShellCard` scan-line rows with ⤢ detail screens.
"""
"""Shell / CmdRun handlers with inline commands and bounded output tails."""

from __future__ import annotations

Expand Down
30 changes: 22 additions & 8 deletions backend/cli/tui/renderer/handlers/task_state.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"""Canonical task-state handlers that keep the Tasks sidebar current."""
"""Canonical task-state handlers for the sidebar and structured transcript card."""

from __future__ import annotations

import json
from typing import TYPE_CHECKING

from backend.ledger.action import TaskStateAction
Expand All @@ -18,6 +17,7 @@ def _handle_task_state_action(
orch: 'RendererEventProcessorMixin', event: TaskStateAction
) -> None:
"""Task-state commands are represented only by the persistent sidebar."""
del orch, event


def _handle_task_state_observation(
Expand All @@ -31,9 +31,23 @@ def _handle_task_state_observation(
orch._last_task_sidebar_signature = None
orch._refresh_tasks_sidebar()

content = str(getattr(event, 'content', '') or '').strip()
if not content:
content = json.dumps(state, indent=2, sort_keys=True, ensure_ascii=False)
from backend.cli.tui.widgets.activity_card import ToolResult

orch._append_transcript_widget(ToolResult('Task state', content))
contract = state.get('contract') if isinstance(state, dict) else None
objective = (
str(contract.get('objective') or '').strip()
if isinstance(contract, dict)
else ''
)
revision = getattr(event, 'revision', None)
if revision is None and isinstance(state, dict):
revision = state.get('revision')

from backend.cli.tui.widgets.scan_line import TaskStateCard

orch._append_scan_line_card(
TaskStateCard(
str(getattr(event, 'command', '') or 'view'),
revision=revision if isinstance(revision, int) else None,
objective=objective,
tasks=list(tasks) if isinstance(tasks, list) else [],
)
)
6 changes: 3 additions & 3 deletions backend/cli/tui/renderer/handlers/terminal.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Terminal session event handlers (run, input, read, observation).
"""Terminal session handlers with inline commands and bounded output tails.

Appends one :class:`TerminalCard` per agent command. A session scrollback
buffer tracks full output for detail screens.
Appends one :class:`TerminalCard` per agent command. A session scrollback
buffer retains complete output for the overflow detail view.
"""

from __future__ import annotations
Expand Down
2 changes: 1 addition & 1 deletion backend/cli/tui/renderer/mixins/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ def _append_transcript_widget(self, widget: Any) -> None:
self._sync_transcript_viewport()

def _append_scan_line_card(self, card: Any) -> Any:
"""Append a 1-line :class:`ScanLineCard` to the transcript feed."""
"""Append a :class:`ScanLineCard` action block to the transcript feed."""
self._flush_orient_burst()
self.commit_live_thinking()
self._register_widget_event_id(card)
Expand Down
3 changes: 0 additions & 3 deletions backend/cli/tui/renderer/mixins/thinking.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,6 @@ def _render_thinking_payload(
self._append_scan_line_card(
PayloadCard(card.verb, card.detail, body or intent.text)
)
from backend.cli.tui.widgets.activity_card import ToolResult

self._append_transcript_widget(ToolResult(card.verb, intent.text))
return True

def _code_artifact_card(self, intent: ThinkingRenderIntent) -> ActivityCard:
Expand Down
1 change: 0 additions & 1 deletion backend/cli/tui/screen/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ async def on_hud_controls_requested(self, event: HUD.ControlsRequested) -> None:
self._apply_autonomy_level(result['autonomy'])
self._apply_hud_reasoning_effort(result['reasoning'])


def _apply_autonomy_level(self, new_level: str) -> None:
if getattr(self, '_hud_autonomy_syncing', False):
return
Expand Down
20 changes: 10 additions & 10 deletions backend/cli/tui/transcript_tiers.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
"""Transcript display tiers for TUI tool rendering.

The live TUI renders tool activity in exactly two tiers:
The live TUI renders tool activity in two tiers:

- **Orient** — a flat single-line :class:`OrientLine` row (no body, no
expansion). Used for lightweight reads / lookups. See
:data:`ORIENT_TOOL_NAMES`.
- **Action** — a single-line :class:`ScanLineCard` summary with a state-colored
left pipe and a ``⤢`` affordance. The full payload (diff, output, scrollback,
stack, result) lives in a pushed full-screen ``DetailScreen``; the feed row
itself never grows. Used for everything heavier than an orient read. See
:data:`ACTION_TOOL_NAMES`.

There is no inline collapsed/expanded body in the live feed — expansion always
means a detail screen on the screen stack (open with Enter/Space on a focused
card, the ``⤢`` button, or a click).
- **Action** — a :class:`ScanLineCard` with a state-colored headline and a
curated inline payload preview. Diffs, commands, task progress, and bounded
output are readable without leaving the transcript. A ``⤢`` affordance can
still open a full-screen ``DetailScreen`` for overflow. Used for everything
heavier than an orient read. See :data:`ACTION_TOOL_NAMES`.

Inline bodies are intentionally bounded so the transcript remains scannable.
Expansion means opening the complete payload on the screen stack (Enter/Space
on a focused card, the ``⤢`` button, or a click).

These name sets are a reference for which tier a tool belongs to. They are not
imported by the render pipeline (which keys off event/observation types in
Expand Down
8 changes: 5 additions & 3 deletions backend/cli/tui/widgets/scan_line/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Scan-line action cards: 1-line transcript rows with detail-screen expand.
"""Transcript action cards with inline payload previews and overflow detail.

Each card mirrors OrientLine/ThinkingIndicator chrome (background #090d18,
left pipe, padding) with fixed ``height: 1`` and a ``⤢`` button that pushes
a :class:`DetailScreen`.
left pipe, padding). Useful payload is visible in the feed; a ``⤢`` button may
push a :class:`DetailScreen` for full overflow content.

Subclasses override ``_line_text()`` and ``build_detail_screen()``.
"""
Expand All @@ -21,6 +21,7 @@
MCPCard,
PayloadCard,
ShellCard,
TaskStateCard,
TerminalCard,
_compact_path,
_extract_syntax_error,
Expand All @@ -38,6 +39,7 @@
'MCPCard',
'PayloadCard',
'ShellCard',
'TaskStateCard',
'TerminalCard',
'BrowserCard',
'CompactionCard',
Expand Down
Loading
Loading