diff --git a/src/timmy/vassal/dispatch.py b/src/timmy/vassal/dispatch.py index b659bb03..d0feb99b 100644 --- a/src/timmy/vassal/dispatch.py +++ b/src/timmy/vassal/dispatch.py @@ -110,6 +110,92 @@ async def _get_or_create_label( return None +# --------------------------------------------------------------------------- +# Dispatch action helpers +# --------------------------------------------------------------------------- + + +async def _apply_label_to_issue( + client: Any, + base_url: str, + headers: dict, + repo: str, + issue_number: int, + label_name: str, +) -> bool: + """Get-or-create the label then apply it to the issue. Returns True on success.""" + label_id = await _get_or_create_label(client, base_url, headers, repo, label_name) + if label_id is None: + return False + resp = await client.post( + f"{base_url}/repos/{repo}/issues/{issue_number}/labels", + headers=headers, + json={"labels": [label_id]}, + ) + return resp.status_code in (200, 201) + + +async def _post_dispatch_comment( + client: Any, + base_url: str, + headers: dict, + repo: str, + issue: TriagedIssue, + label_name: str, +) -> bool: + """Post the vassal routing comment. Returns True on success.""" + agent_name = issue.agent_target.value.capitalize() + comment_body = ( + f"🤖 **Vassal dispatch** → routed to **{agent_name}**\n\n" + f"Priority score: {issue.priority_score} \n" + f"Rationale: {issue.rationale} \n" + f"Label: `{label_name}`" + ) + resp = await client.post( + f"{base_url}/repos/{repo}/issues/{issue.number}/comments", + headers=headers, + json={"body": comment_body}, + ) + return resp.status_code in (200, 201) + + +async def _perform_gitea_dispatch( + issue: TriagedIssue, + record: DispatchRecord, +) -> None: + """Apply label and post comment via Gitea. Mutates *record* in-place.""" + try: + import httpx + + from config import settings + except ImportError as exc: + logger.warning("dispatch_issue: missing dependency — %s", exc) + return + + if not settings.gitea_enabled or not settings.gitea_token: + logger.info("dispatch_issue: Gitea disabled — skipping label/comment") + return + + base_url = f"{settings.gitea_url}/api/v1" + repo = settings.gitea_repo + headers = { + "Authorization": f"token {settings.gitea_token}", + "Content-Type": "application/json", + } + label_name = _LABEL_MAP[issue.agent_target] + + try: + async with httpx.AsyncClient(timeout=15) as client: + record.label_applied = await _apply_label_to_issue( + client, base_url, headers, repo, issue.number, label_name + ) + record.comment_posted = await _post_dispatch_comment( + client, base_url, headers, repo, issue, label_name + ) + except Exception as exc: + logger.warning("dispatch_issue: Gitea action failed — %s", exc) + + # --------------------------------------------------------------------------- # Dispatch action # --------------------------------------------------------------------------- @@ -144,58 +230,7 @@ async def dispatch_issue(issue: TriagedIssue) -> DispatchRecord: _registry[issue.number] = record return record - try: - import httpx - - from config import settings - except ImportError as exc: - logger.warning("dispatch_issue: missing dependency — %s", exc) - _registry[issue.number] = record - return record - - if not settings.gitea_enabled or not settings.gitea_token: - logger.info("dispatch_issue: Gitea disabled — skipping label/comment") - _registry[issue.number] = record - return record - - base_url = f"{settings.gitea_url}/api/v1" - repo = settings.gitea_repo - headers = { - "Authorization": f"token {settings.gitea_token}", - "Content-Type": "application/json", - } - label_name = _LABEL_MAP[issue.agent_target] - - try: - async with httpx.AsyncClient(timeout=15) as client: - label_id = await _get_or_create_label(client, base_url, headers, repo, label_name) - - # Apply label - if label_id is not None: - resp = await client.post( - f"{base_url}/repos/{repo}/issues/{issue.number}/labels", - headers=headers, - json={"labels": [label_id]}, - ) - record.label_applied = resp.status_code in (200, 201) - - # Post routing comment - agent_name = issue.agent_target.value.capitalize() - comment_body = ( - f"🤖 **Vassal dispatch** → routed to **{agent_name}**\n\n" - f"Priority score: {issue.priority_score} \n" - f"Rationale: {issue.rationale} \n" - f"Label: `{label_name}`" - ) - resp = await client.post( - f"{base_url}/repos/{repo}/issues/{issue.number}/comments", - headers=headers, - json={"body": comment_body}, - ) - record.comment_posted = resp.status_code in (200, 201) - - except Exception as exc: - logger.warning("dispatch_issue: Gitea action failed — %s", exc) + await _perform_gitea_dispatch(issue, record) _registry[issue.number] = record logger.info(