Skip to content
Merged
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
5 changes: 4 additions & 1 deletion __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ def register(registry) -> None:
from .review_tools import get_review_tools
from .write_tools import get_write_tools

write = get_write_tools(default_repo) + get_review_tools(default_repo)
# Pass the host's event-bus seam (ADR 0039) when it exists so PR lifecycle
# events broadcast as `github.pr.opened` / `github.pr.merged`. hasattr-guarded
# like the other host couplings — an older host just gets no events.
write = get_write_tools(default_repo, emit=getattr(registry, "emit", None)) + get_review_tools(default_repo)
for t in write:
registry.register_tool(t)
n_write = len(write)
Expand Down
31 changes: 28 additions & 3 deletions write_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,28 @@ def _csv(value: str) -> list[str]:
return [tok.strip() for tok in (value or "").split(",") if tok.strip()]


def get_write_tools(default_repo: str = "") -> list:
def _pr_number(url: str) -> str:
"""The PR number from a ``…/pull/<n>`` URL, or "" when it isn't one."""
tail = (url or "").rstrip("/").rsplit("/", 1)[-1]
return tail if tail.isdigit() else ""


def get_write_tools(default_repo: str = "", emit=None) -> list:
"""Build the write tools. ``default_repo`` (``owner/name``) is used whenever a tool's
``repo`` arg is omitted, so an agent with one configured repo needn't repeat it."""
``repo`` arg is omitted, so an agent with one configured repo needn't repeat it.

``emit`` is the host's namespaced event-bus seam (ADR 0039; ``registry.emit``). When
given, PR lifecycle events are broadcast as ``github.pr.opened`` / ``github.pr.merged``
so consumers (e.g. a fleet activity feed) can surface them without polling. Optional
and fully guarded — an older host, or a bus error, never fails a tool."""

def _emit(topic: str, data: dict) -> None:
if not emit:
return
try:
emit(topic, data)
except Exception: # noqa: BLE001 — telemetry must never break a write tool
pass

@tool
async def github_create_issue(title: str, repo: str = "", body: str = "", labels: str = "") -> str:
Expand Down Expand Up @@ -118,7 +137,12 @@ async def github_create_pr(head: str, title: str, repo: str = "", body: str = ""
rc, out, serr = await run_gh(args)
if gh_err := check_gh_error(rc, serr):
return gh_err
return out.strip()
url = out.strip()
_emit(
"pr.opened",
{"repo": repo, "number": _pr_number(url), "title": title, "url": url, "head": head, "base": base},
)
return url

@tool
async def github_edit_pr(number: int, repo: str = "", title: str = "", body: str = "", state: str = "") -> str:
Expand Down Expand Up @@ -203,6 +227,7 @@ async def github_merge_pr(
rc, out, serr = await run_gh(args)
if gh_err := check_gh_error(rc, serr):
return gh_err
_emit("pr.merged", {"repo": repo, "number": str(number), "method": method})
return out.strip() or f"Merged PR #{number} in {repo} via {method}."

@tool
Expand Down
Loading