added file permissions and fixed a duplicate output bug.
This commit is contained in:
46
app/services/permissions.py
Normal file
46
app/services/permissions.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Permission gating for tool execution."""
|
||||
|
||||
import logging
|
||||
|
||||
from rich.prompt import Confirm
|
||||
|
||||
from app.models.config import PermissionsConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PermissionDenied(Exception):
|
||||
"""Raised when a tool is denied execution by permissions policy."""
|
||||
|
||||
|
||||
class PermissionsService:
|
||||
"""Check whether a tool is allowed to execute based on config tiers."""
|
||||
|
||||
def __init__(self, config: PermissionsConfig) -> None:
|
||||
self.config = config
|
||||
|
||||
def check(self, tool_name: str, description: str = "") -> bool:
|
||||
"""Check if a tool is permitted to run.
|
||||
|
||||
Returns:
|
||||
True if permitted, False if denied.
|
||||
"""
|
||||
if tool_name in self.config.deny:
|
||||
logger.info("Tool '%s' is in deny list — blocked", tool_name)
|
||||
return False
|
||||
|
||||
if tool_name in self.config.auto_approve:
|
||||
logger.debug("Tool '%s' is auto-approved", tool_name)
|
||||
return True
|
||||
|
||||
# Explicit prompt_user list or unlisted tools both trigger a prompt
|
||||
return self._prompt_user(tool_name, description)
|
||||
|
||||
def _prompt_user(self, tool_name: str, description: str) -> bool:
|
||||
"""Prompt the user for approval via the terminal."""
|
||||
prompt_text = f"Allow tool [bold]{tool_name}[/bold]"
|
||||
if description:
|
||||
prompt_text += f" — {description}"
|
||||
prompt_text += "?"
|
||||
|
||||
return Confirm.ask(prompt_text, default=False)
|
||||
@@ -4,11 +4,11 @@ from collections.abc import AsyncIterator
|
||||
|
||||
from rich.live import Live
|
||||
from rich.markdown import Markdown
|
||||
from rich.panel import Panel
|
||||
|
||||
from app.models.config import DisplayConfig
|
||||
from app.models.message import Message
|
||||
from app.models.tool_call import ToolCall, ToolCallFunction
|
||||
from app.utils.display import print_assistant_message
|
||||
from app.utils.logging import console, get_logger
|
||||
from app.utils.token_counter import TokenUsage
|
||||
|
||||
@@ -50,14 +50,19 @@ class StreamHandler:
|
||||
# Show reasoning while waiting for content
|
||||
display_text = self._accumulated_content
|
||||
if not display_text and self._accumulated_reasoning:
|
||||
display_text = f"*thinking...*"
|
||||
display_text = "*thinking...*"
|
||||
|
||||
if display_text and self._display_config.stream_output:
|
||||
live.update(Markdown(display_text))
|
||||
|
||||
# Final static render
|
||||
if self._accumulated_content:
|
||||
print_assistant_message(self._accumulated_content)
|
||||
# Render inside the same Assistant panel used for final output
|
||||
# so the live display and final frame are visually consistent
|
||||
live.update(
|
||||
Panel(
|
||||
Markdown(display_text),
|
||||
title="Assistant",
|
||||
border_style="green",
|
||||
expand=True,
|
||||
)
|
||||
)
|
||||
|
||||
tool_calls = self._build_tool_calls() or None
|
||||
return Message(
|
||||
|
||||
Reference in New Issue
Block a user