Files
agent-framework/python/samples/02-agents/harness/console/components/text_input.py
T
westey bad05a2bdc Python: Harness console for python (#6312)
* Add initial harness console for python

* Add textual to project

* Add planning and approval flows with list selector

* Address PR comments

* Fix list selection bug

* Fix PR #6312 round 2 review comments

- Escape untrusted agent text with rich.markup.escape() in observers
  (text_output, planning_output, reasoning_display) to prevent markup injection
- Remove non-functional 'Always approve' choices from tool_approval.py
  (framework lacks CreateAlwaysApproveToolResponse support)
- Remove textual from root pyproject.toml dev deps (sample-specific)
- Add PEP 723 inline script metadata to harness_research.py
- Narrow except Exception to except NoMatches in list_selection.py

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix build error

* Fix build errors

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-09 05:48:35 +00:00

103 lines
3.1 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
"""Text input widget with inline prompt for the harness console."""
from __future__ import annotations
from textual import on
from textual.app import ComposeResult
from textual.containers import Horizontal
from textual.message import Message
from textual.reactive import reactive
from textual.widget import Widget
from textual.widgets import Input, Label
class HarnessTextInput(Widget):
"""Text input widget with a prompt label on the left.
Displays a prompt (e.g., "> ") followed by a borderless input field.
Sits between the two mode-colored horizontal rules.
Attributes:
prompt: The prompt text displayed on the left (e.g., "> ").
placeholder: Placeholder text shown when the input is empty.
"""
prompt: reactive[str] = reactive("> ")
placeholder: reactive[str] = reactive("")
class Submitted(Message):
"""Message sent when the input is submitted.
Attributes:
value: The submitted text value.
"""
def __init__(self, value: str) -> None:
"""Initialize the Submitted message.
Args:
value: The submitted text value.
"""
self.value = value
super().__init__()
def compose(self) -> ComposeResult:
"""Compose the prompt label and input field.
Yields:
A horizontal container with the prompt and input field.
"""
with Horizontal(classes="prompt-container"):
yield Label(self.prompt, classes="prompt-label", id="prompt-label")
yield Input(placeholder=self.placeholder, classes="input-field", id="input-field")
def watch_prompt(self, new_prompt: str) -> None:
"""Update the prompt label when the prompt attribute changes.
Args:
new_prompt: The new prompt text.
"""
try:
label = self.query_one("#prompt-label", Label)
label.update(new_prompt)
except Exception:
pass
def watch_placeholder(self, new_placeholder: str) -> None:
"""Update the input placeholder when the placeholder attribute changes.
Args:
new_placeholder: The new placeholder text.
"""
try:
input_field = self.query_one("#input-field", Input)
input_field.placeholder = new_placeholder
except Exception:
# Input doesn't exist yet (before compose), ignore
pass
@on(Input.Submitted)
def on_input_submitted(self, event: Input.Submitted) -> None:
"""Handle input submission.
Clears the input field and posts a Submitted message with the value.
Args:
event: The Input.Submitted event.
"""
value = event.value
event.input.clear()
self.post_message(self.Submitted(value))
def focus_input(self) -> None:
"""Focus the input field."""
input_field = self.query_one(".input-field", Input)
input_field.focus()
def clear_input(self) -> None:
"""Clear the input field."""
input_field = self.query_one(".input-field", Input)
input_field.clear()