Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Jean-Baptiste Muscat <jeanbaptiste.muscatdupuis@mistral.ai>
Co-authored-by: JeroenvdV <JeroenvdV@users.noreply.github.com>
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Paul Cacheux <paul.cacheux@mistral.ai>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Simon Van de Kerckhove <simon.vandekerckhove@mistral.ai>
Co-authored-by: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: allansimon-mistral <allan.simon@ext.mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Clément Sirieix 2026-05-05 14:40:11 +02:00 committed by GitHub
parent 71f373c60c
commit 4972dd5694
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 2892 additions and 842 deletions

View file

@ -20,6 +20,8 @@ class ApprovalApp(Container):
can_focus = True
can_focus_children = False
NUM_OPTIONS = 4
BINDINGS: ClassVar[list[BindingType]] = [
Binding("up", "move_up", "Up", show=False),
Binding("down", "move_down", "Down", show=False),
@ -27,8 +29,9 @@ class ApprovalApp(Container):
Binding("1", "select_1", "Yes", show=False),
Binding("y", "select_1", "Yes", show=False),
Binding("2", "select_2", "Always Tool Session", show=False),
Binding("3", "select_3", "No", show=False),
Binding("n", "select_3", "No", show=False),
Binding("3", "select_3", "Always Permanent", show=False),
Binding("4", "select_4", "No", show=False),
Binding("n", "select_4", "No", show=False),
]
class ApprovalGranted(Message):
@ -49,6 +52,18 @@ class ApprovalApp(Container):
self.tool_args = tool_args
self.required_permissions = required_permissions
class ApprovalGrantedAlwaysPermanent(Message):
def __init__(
self,
tool_name: str,
tool_args: BaseModel,
required_permissions: list[RequiredPermission],
) -> None:
super().__init__()
self.tool_name = tool_name
self.tool_args = tool_args
self.required_permissions = required_permissions
class ApprovalRejected(Message):
def __init__(self, tool_name: str, tool_args: BaseModel) -> None:
super().__init__()
@ -77,20 +92,18 @@ class ApprovalApp(Container):
def compose(self) -> ComposeResult:
with Vertical(id="approval-options"):
yield NoMarkupStatic("")
for _ in range(3):
for _ in range(self.NUM_OPTIONS):
widget = NoMarkupStatic("", classes="approval-option")
self.option_widgets.append(widget)
yield widget
yield NoMarkupStatic("")
self.help_widget = NoMarkupStatic(
"↑↓ navigate Enter select ESC reject", classes="approval-help"
)
yield self.help_widget
with Vertical(id="approval-content"):
self.title_widget = NoMarkupStatic(
f"{self.tool_name} command", classes="approval-title"
)
title = self._build_title()
self.title_widget = NoMarkupStatic(title, classes="approval-title")
yield self.title_widget
with VerticalScroll(classes="approval-tool-info-scroll"):
@ -99,6 +112,12 @@ class ApprovalApp(Container):
)
yield self.tool_info_container
def _build_title(self) -> str:
if self.required_permissions:
labels = ", ".join(rp.label for rp in self.required_permissions)
return f"Permission for the {self.tool_name} tool ({labels})"
return f"Permission for the {self.tool_name} tool"
async def on_mount(self) -> None:
await self._update_tool_info()
self._update_options()
@ -113,16 +132,11 @@ class ApprovalApp(Container):
await self.tool_info_container.mount(approval_widget)
def _update_options(self) -> None:
if self.required_permissions:
labels = ", ".join(rp.label for rp in self.required_permissions)
always_text = f"Yes and always allow for this session: {labels}"
else:
always_text = f"Yes and always allow {self.tool_name} for this session"
options = [
("Yes", "yes"),
(always_text, "yes"),
("No and tell the agent what to do instead", "no"),
("Allow once", "yes"),
("Allow for remainder of this session", "yes"),
("Always allow", "yes"),
("Deny", "no"),
]
for idx, ((text, color_type), widget) in enumerate(
@ -154,11 +168,11 @@ class ApprovalApp(Container):
widget.add_class("approval-option-no")
def action_move_up(self) -> None:
self.selected_option = (self.selected_option - 1) % 3
self.selected_option = (self.selected_option - 1) % self.NUM_OPTIONS
self._update_options()
def action_move_down(self) -> None:
self.selected_option = (self.selected_option + 1) % 3
self.selected_option = (self.selected_option + 1) % self.NUM_OPTIONS
self._update_options()
def action_select(self) -> None:
@ -176,9 +190,13 @@ class ApprovalApp(Container):
self.selected_option = 2
self._handle_selection(2)
def action_select_4(self) -> None:
self.selected_option = 3
self._handle_selection(3)
def action_reject(self) -> None:
self.selected_option = 2
self._handle_selection(2)
self.selected_option = 3
self._handle_selection(3)
def _handle_selection(self, option: int) -> None:
match option:
@ -197,6 +215,14 @@ class ApprovalApp(Container):
)
)
case 2:
self.post_message(
self.ApprovalGrantedAlwaysPermanent(
tool_name=self.tool_name,
tool_args=self.tool_args,
required_permissions=self.required_permissions,
)
)
case 3:
self.post_message(
self.ApprovalRejected(
tool_name=self.tool_name, tool_args=self.tool_args