Skip to content

trading_agent.terminal.widgets.permission_card

The card for a tool call that needs the user's permission, as the web page's PermissionCard.tsx and Claude Code show it.

The card names the tool and its arguments and counts down to the moment the request lapses. The choices are Yes (once), Yes and don't ask again for this tool on this desk (until revoked on the Settings screen), and No with an optional reason that goes to the desk.

Keys: Up and Down move, Enter or 1 to 3 decides, Tab moves to the reason box and back. Typing a letter while the choices have the focus goes into the reason box, so a reason being typed can never be lost and Enter can never pick Yes in its place.

Typical usage example:

card = PermissionCard(request, gateway)

PermissionCard

PermissionCard(request, gateway)

Bases: Vertical

A card for allowing or denying one tool call.

Creates the card.

Parameters:

Name Type Description Default
request dict[str, Any]

The pending permission request.

required
gateway GatewayClient

The gateway, to send the decision.

required
Source code in src/trading_agent/terminal/widgets/permission_card.py
def __init__(
    self,
    request: dict[str, Any],
    gateway: gateway_client.GatewayClient,
):
    """Creates the card.

    Args:
        request (dict[str, Any]): The pending permission request.
        gateway (gateway_client.GatewayClient): The gateway, to send the decision.
    """
    super().__init__(classes='request-card permission-card')
    self.request_id = request['request_id']
    self._request = request
    self._gateway = gateway
    self._sending = False
    self._formatter = formatter.Formatter()

Done

Bases: Message

The request was decided.

compose

compose()

Builds the card.

Yields:

Type Description
Widget

The heading with the countdown, the tool and its arguments, the choices, the reason box, the error and the hint.

Source code in src/trading_agent/terminal/widgets/permission_card.py
def compose(self):
    """Builds the card.

    Yields:
        Widget: The heading with the countdown, the tool and its arguments, the choices, the reason box, the error and the hint.
    """
    content = self._request.get('content') or {}
    tool_name = content.get('tool_name', 'a tool')
    desk = self._request.get('desk', '')
    yield widgets.Static('', classes='request-heading')
    yield widgets.Static(
        Content.from_markup('Run [b $accent]$tool[/]?', tool=tool_name),
        classes='question-text',
    )
    yield widgets.Static(
        json.dumps(
            content.get('arguments') or {}, indent=2, ensure_ascii=False
        ),
        markup=False,
        classes='permission-arguments',
    )
    yield widgets.OptionList(
        option_list.Option(
            Content.from_markup(
                '1  [b]Yes[/b]  [dim]run this call once, through the risk engine as usual[/dim]'
            )
        ),
        option_list.Option(
            Content.from_markup(
                "2  [b]Yes, and don't ask again for $tool on $desk[/b]  [dim]until you revoke it on Settings[/dim]",
                tool=tool_name,
                desk=desk,
            )
        ),
        option_list.Option(
            Content.from_markup(
                '3  [b]No[/b]  [dim]tell the desk why in the box below (optional)[/dim]'
            )
        ),
        classes='question-options',
    )
    yield widgets.Input(
        placeholder='Reason for No, sent to the desk (optional)',
        classes='permission-reason',
        select_on_focus=False,
    )
    yield widgets.Static('', classes='request-error')
    yield widgets.Static(
        '[dim]↑↓ to move · enter or 1-3 to decide · tab to the reason box[/dim]',
        classes='request-hint',
    )

on_mount

on_mount()

Starts the countdown.

Source code in src/trading_agent/terminal/widgets/permission_card.py
def on_mount(self) -> None:
    """Starts the countdown."""
    self._show_countdown()
    self.set_interval(1.0, self._show_countdown)

focus_card

focus_card()

Puts the focus on the choices, so the card's keys work.

Source code in src/trading_agent/terminal/widgets/permission_card.py
def focus_card(self) -> None:
    """Puts the focus on the choices, so the card's keys work."""
    self.query_one('.question-options', widgets.OptionList).focus()

on_key

on_key(event)

Decides with a number key.

Parameters:

Name Type Description Default
event Key

The key press.

required
Source code in src/trading_agent/terminal/widgets/permission_card.py
def on_key(self, event: events.Key) -> None:
    """Decides with a number key.

    Args:
        event (events.Key): The key press.
    """
    if isinstance(self.screen.focused, widgets.Input):
        return
    if (
        event.is_printable
        and event.character
        and not event.character.isdigit()
    ):
        event.stop()
        box = self.query_one('.permission-reason', widgets.Input)
        box.focus()
        box.insert_text_at_cursor(event.character)
        return
    if event.character in ('1', '2', '3'):
        event.stop()
        self._decide(_DECISIONS[int(event.character) - 1])

action_focus_box

action_focus_box()

Moves to the reason box, or back to the choices from it.

Source code in src/trading_agent/terminal/widgets/permission_card.py
def action_focus_box(self) -> None:
    """Moves to the reason box, or back to the choices from it."""
    box = self.query_one('.permission-reason', widgets.Input)
    if self.screen.focused is box:
        self.focus_card()
        return
    box.focus()

on_option_list_option_selected

on_option_list_option_selected(event)

Decides with the selected choice.

Parameters:

Name Type Description Default
event OptionSelected

The selection.

required
Source code in src/trading_agent/terminal/widgets/permission_card.py
def on_option_list_option_selected(
    self, event: widgets.OptionList.OptionSelected
) -> None:
    """Decides with the selected choice.

    Args:
        event (widgets.OptionList.OptionSelected): The selection.
    """
    event.stop()
    self._decide(_DECISIONS[event.option_index])

on_input_submitted

on_input_submitted(event)

Denies with the typed reason when Enter is pressed in the box.

Parameters:

Name Type Description Default
event Submitted

The submission.

required
Source code in src/trading_agent/terminal/widgets/permission_card.py
def on_input_submitted(self, event: widgets.Input.Submitted) -> None:
    """Denies with the typed reason when Enter is pressed in the box.

    Args:
        event (widgets.Input.Submitted): The submission.
    """
    event.stop()
    self._decide('deny')