Skip to content

trading_agent.terminal.widgets.plan_card

The card for a plan a desk presents for approval (present_plan), as the web page's PlanCard.tsx shows it.

The plan is drawn as Markdown. The user approves it, optionally with notes, or sends it back with feedback, which "Keep planning" requires. The desk does not act on the plan until it is approved.

Keys: 1 or Enter on "Approve" approves, 2 keeps planning, Tab moves to the feedback box and back, Ctrl+X dismisses without deciding. Typing a letter while the choices have the focus goes into the feedback box.

Typical usage example:

card = PlanCard(request, gateway)

PlanCard

PlanCard(request, gateway)

Bases: Vertical

A card for approving or revising the desk's plan.

Creates the card.

Parameters:

Name Type Description Default
request dict[str, Any]

The pending plan request.

required
gateway GatewayClient

The gateway, to send the decision.

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

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

Done

Bases: Message

The request was decided or dismissed.

compose

compose()

Builds the card.

Yields:

Type Description
Widget

The heading, title, plan, choices, feedback box, error and hint.

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

    Yields:
        Widget: The heading, title, plan, choices, feedback box, error and hint.
    """
    content = self._request.get('content') or {}
    yield widgets.Static(
        Content.from_markup(
            '[b]$desk[/b] presents a plan for your approval',
            desk=self._request.get('desk', ''),
        ),
        classes='request-heading',
    )
    yield widgets.Static(
        Content.from_markup(
            '[b]$title[/b]', title=content.get('title', '')
        ),
        classes='question-text',
    )
    with containers.VerticalScroll(classes='plan-body'):
        yield widgets.Markdown(content.get('plan', ''))
    yield widgets.OptionList(
        option_list.Option(
            Content.from_markup(
                '1  [b]Approve[/b]  [dim]the desk may act on the plan; feedback is optional[/dim]'
            )
        ),
        option_list.Option(
            Content.from_markup(
                '2  [b]Keep planning[/b]  [dim]send it back with the feedback below[/dim]'
            )
        ),
        classes='question-options',
    )
    yield widgets.Input(
        placeholder='Feedback for the desk: needed for Keep planning, optional with Approve',
        classes='plan-feedback',
        select_on_focus=False,
    )
    yield widgets.Static('', classes='request-error')
    yield widgets.Static(
        '[dim]↑↓ to move · enter or 1-2 to choose · tab to the feedback box · ctrl+x to dismiss[/dim]',
        classes='request-hint',
    )

focus_card

focus_card()

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

Source code in src/trading_agent/terminal/widgets/plan_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/plan_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('.plan-feedback', widgets.Input)
        box.focus()
        box.insert_text_at_cursor(event.character)
        return
    if event.character in ('1', '2'):
        event.stop()
        self._decide(_DECISIONS[int(event.character) - 1])

action_focus_box

action_focus_box()

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

Source code in src/trading_agent/terminal/widgets/plan_card.py
def action_focus_box(self) -> None:
    """Moves to the feedback box, or back to the choices from it."""
    box = self.query_one('.plan-feedback', 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/plan_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)

Sends the plan back with the feedback 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/plan_card.py
def on_input_submitted(self, event: widgets.Input.Submitted) -> None:
    """Sends the plan back with the feedback when Enter is pressed in the box.

    Args:
        event (widgets.Input.Submitted): The submission.
    """
    event.stop()
    if event.value.strip():
        self._decide('revise')

action_dismiss_request

action_dismiss_request()

Dismisses the plan without deciding; the desk uses its own judgment.

Source code in src/trading_agent/terminal/widgets/plan_card.py
def action_dismiss_request(self) -> None:
    """Dismisses the plan without deciding; the desk uses its own judgment."""
    self._dismiss()