Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
# Code owners for specific Hackbot agents
/agents/frontend-triage/ @mozilla/hackbot-frontend
/agents/autowebcompat-repro/ @mozilla/hackbot-webcompat
/agents/autowebcompat-diagnosis/ @mozilla/hackbot-webcompat
86 changes: 86 additions & 0 deletions agents/autowebcompat-diagnosis/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
FROM python:3.12 AS builder

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

ENV UV_PROJECT_ENVIRONMENT=/opt/venv

WORKDIR /app

# Install external deps without building workspace members.
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=VERSION,target=VERSION \
uv sync --frozen --no-dev --no-install-workspace --package hackbot-agent-autowebcompat-diagnosis

RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,target=/app,rw \
uv sync --locked --no-dev --no-editable --package hackbot-agent-autowebcompat-diagnosis

FROM python:3.12 AS base

WORKDIR /app

ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PATH="/opt/venv/bin:$PATH"

FROM base AS agent

# The agent needs Node.js + npm to run the DevTools MCP servers (npm packages;
# the python base ships neither) and the shared libraries the browsers require
# to run headless. The browser binaries themselves are downloaded at agent
# startup (a fresh build per run): Firefox via mozdownload/mozinstall and Chrome
# for Testing via the Chrome-for-Testing JSON API (see browser.py).
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
# Node.js, to run the Firefox and Chrome DevTools MCP servers.
nodejs npm \
# Used to download the reproduction script attached to the bug.
curl \
# Shared by both browsers.
ca-certificates \
# Firefox runtime deps.
libgtk-3-0 libdbus-glib-1-2 libx11-xcb1 libxtst6 libxt6 libpci3 \
# Chrome runtime deps.
libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 libatspi2.0-0t64 \
libcups2t64 libdbus-1-3 libgbm1 libxcomposite1 libxdamage1 libxfixes3 \
libxrandr2 libxkbcommon0 libasound2t64 libpango-1.0-0 libcairo2 \
fonts-liberation \
&& rm -rf /var/lib/apt/lists/*

# Install the DevTools MCP servers and Puppeteer from the pinned
# package.json/lockfile. PUPPETEER_SKIP_DOWNLOAD stops Puppeteer's install
# script from fetching its own bundled Chrome: the browsers this agent drives
# are downloaded at startup instead (see browser.py).
COPY agents/autowebcompat-diagnosis/package.json \
agents/autowebcompat-diagnosis/package-lock.json \
agents/autowebcompat-diagnosis/repro_reference.mjs \
/app/diagnosis/
RUN cd /app/diagnosis && PUPPETEER_SKIP_DOWNLOAD=1 npm ci --omit=dev

# hackbot.toml lives at the agent root (not inside the package), so copy it into
# the working dir; the runtime discovers it there (cwd) at startup.
COPY agents/autowebcompat-diagnosis/hackbot.toml /app/hackbot.toml

RUN useradd --create-home --shell /bin/bash agent \
&& mkdir -p /workspace \
&& chown agent:agent /workspace /app/diagnosis

USER agent

COPY --from=builder /opt/venv /opt/venv

CMD ["python", "-m", "hackbot_agents.autowebcompat_diagnosis"]

FROM base AS broker

RUN useradd --create-home --shell /bin/bash broker

USER broker

EXPOSE 8765

COPY --from=builder /opt/venv /opt/venv

CMD ["python", "-m", "hackbot_agents.autowebcompat_diagnosis.broker"]
31 changes: 31 additions & 0 deletions agents/autowebcompat-diagnosis/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
services:
autowebcompat-diagnosis-broker:
build:
context: ../..
dockerfile: agents/autowebcompat-diagnosis/Dockerfile
target: broker
environment:
BUGZILLA_API_URL: ${BUGZILLA_API_URL}
BUGZILLA_API_KEY: ${BUGZILLA_API_KEY}
expose:
- "8765"

autowebcompat-diagnosis-agent:
build:
context: ../..
dockerfile: agents/autowebcompat-diagnosis/Dockerfile
target: agent
environment:
- RUN_ID
- BUG_DATA
- BUG_ID
- BROKER_URL=http://autowebcompat-diagnosis-broker:8765
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:?error}
# No uploader locally: summary/logs/attachments are written under
# /artifacts/<run_id>, bind-mounted to the host's ~/hackbot/artifacts.
- ARTIFACTS_DIR=/artifacts
volumes:
- ${HOME}/hackbot/artifacts:/artifacts
depends_on:
autowebcompat-diagnosis-broker:
condition: service_started
3 changes: 3 additions & 0 deletions agents/autowebcompat-diagnosis/hackbot.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# autowebcompat-diagnosis needs no platform prep: no [source] checkout, no [firefox] build.
# Subject comes from the request (bug_data / bug_id); the DevTools MCP drives a
# Firefox instance installed at startup.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import logging
from datetime import datetime
from typing import Literal

from hackbot_runtime import (
HackbotAgentResult,
HackbotContext,
run_async,
)
from pydantic_settings import BaseSettings, SettingsConfigDict

from .agent import (
AutowebcompatDiagnosisResult,
BugDataInput,
BugIdInput,
RunTracker,
TaskConfig,
run_autowebcompat_diagnosis,
)

logger = logging.getLogger("autowebcompat-diagnosis")


class AgentInputs(BaseSettings):
broker_url: str
bug_data: str | None = None
bug_id: int | None = None
model: str | None = None
max_turns: int | None = None
effort: (
Literal["low"]
| Literal["medium"]
| Literal["high"]
| Literal["xhigh"]
| Literal["max"]
| None
) = None

model_config = SettingsConfigDict(extra="ignore")

@property
def bugzilla_mcp_url(self) -> str:
return f"{self.broker_url.rstrip('/')}/mcp"


class AutowebcompatResult(HackbotAgentResult):
result: AutowebcompatDiagnosisResult
start_time: datetime
end_time: datetime


async def main(ctx: HackbotContext) -> AutowebcompatResult:
start_time = datetime.now()
inputs = AgentInputs() # type: ignore

if inputs.bug_data is not None:
input_data: BugDataInput | BugIdInput = BugDataInput(bug_data=inputs.bug_data)
elif inputs.bug_id is not None:
input_data = BugIdInput(bug_id=inputs.bug_id)

tracker = RunTracker()
result = await run_autowebcompat_diagnosis(
TaskConfig(
model=inputs.model,
max_turns=inputs.max_turns,
effort=inputs.effort,
log=ctx.log_path,
verbose=True,
),
tracker,
input_data,
bugzilla_mcp_server={
"type": "http",
"url": inputs.bugzilla_mcp_url,
},
publish_file=ctx.publish_file,
)
end_time = datetime.now()

result = AutowebcompatResult(
result=result,
num_turns=tracker.num_turns,
total_cost_usd=tracker.total_cost_usd,
start_time=start_time,
end_time=end_time,
)
logger.info("Run completed with result: %s", result)
return result


if __name__ == "__main__":
run_async(main)
Loading