feat: implement NotificationReaderAgent to pre-screen Gitea notifications (#2)

This commit was merged in pull request #2.
This commit is contained in:
2026-06-30 22:01:07 +02:00
parent 287d20bc56
commit 9b72e20d5a
7 changed files with 288 additions and 2 deletions
+50 -2
View File
@@ -14,6 +14,9 @@ from gitea.models import IssueModel, PullRequestModel, RepositoryModel
from gitea.tools.gitea_tools import GiteaTools
from gitea.config import AGENT_MODEL_ID, AGENT_MAX_RETRIES
from gitea.workspace import WorkspaceManager
from core.factory import AgentFactory
from core.notification_tools import NotificationTools
from core.notification_agent import NotificationNoToolCalledError
logger: logging.Logger = logging.getLogger("agent-orchestrator")
@@ -33,6 +36,9 @@ class AgentOrchestrator:
self._model_name = model_name
self._work_queue = WorkQueue()
self._dispatcher = AgentDispatcher(client, tools, model_name, max_retries)
self._notification_reader = AgentFactory.create_notification_reader_agent(model_name)
self._max_retries = max_retries
def _get_state_file_path(self) -> Path:
"""Get the path to the persistent state file."""
@@ -74,6 +80,13 @@ class AgentOrchestrator:
# Filter and enqueue tasks from notifications
latest_timestamp = last_checked
inspection_tools = [
self._tools.get_issue,
self._tools.get_pull_request,
self._tools.get_issue_comments,
self._tools.get_pull_request_comments,
]
for n in notifications:
notification_id = n.get("id")
subject = n.get("subject") or {}
@@ -97,7 +110,41 @@ class AgentOrchestrator:
logger.warning(f"Could not parse task number from subject URL: {subj_url}")
continue
if subj_type == "issue":
# Run NotificationReaderAgent to pre-screen the notification
mission = (
f"Analyze Gitea notification ID {notification_id}.\n"
f"Repository: {repo_full_name}\n"
f"Subject Type: {subj_type}\n"
f"Task Number: {task_number}\n"
f"Please decide if we should process or skip this notification."
)
notification_tools = NotificationTools()
attempt = 0
attempt_limit = self._max_retries
success = False
while attempt < attempt_limit:
attempt += 1
try:
await self._notification_reader.decide_notification(
mission,
inspection_tools,
notification_tools
)
success = True
break
except NotificationNoToolCalledError as e:
logger.error(f"Notification reader error on notification {notification_id} (attempt {attempt}/{attempt_limit}): {e}")
if not success or notification_tools.action == "SKIP":
reason = notification_tools.arguments.get("reason", "Failed to call routing tool / default skip")
logger.info(f"Skipping notification {notification_id} for {repo_full_name}#{task_number}. Reason: {reason}")
if notification_id is not None:
self._client.mark_notification_as_read(notification_id)
logger.info(f"Marked skipped Gitea notification thread {notification_id} as read.")
continue
# Route based on decided action
if notification_tools.action == "PROCESS_ISSUE":
try:
issue = self._client.get_issue(owner, repo_name, task_number)
if issue.repository is None:
@@ -114,7 +161,7 @@ class AgentOrchestrator:
self._work_queue.enqueue(item)
except Exception as e:
logger.error(f"Failed to fetch issue #{task_number} for notification: {e}")
elif subj_type in ("pull", "pullrequest"):
elif notification_tools.action == "PROCESS_PR":
try:
pr = self._client.get_pull_request(owner, repo_name, task_number)
if pr.repository is None:
@@ -132,6 +179,7 @@ class AgentOrchestrator:
except Exception as e:
logger.error(f"Failed to fetch PR #{task_number} for notification: {e}")
# Process enqueued work
if not self._work_queue.is_empty:
await self._process_work()