mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 11:33:14 +00:00
feat: include plan links in PR descriptions (#1613)
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
parent
69148f54f5
commit
e5a29eca8c
2 changed files with 107 additions and 19 deletions
|
|
@ -11,6 +11,7 @@ from langgraph.config import get_config
|
|||
from langgraph_sdk import get_client
|
||||
|
||||
from ..dashboard.agent_usage import record_agent_pr_usage
|
||||
from ..utils.dashboard_links import dashboard_plan_url
|
||||
from ..utils.github_app import get_github_app_installation_token
|
||||
from ..utils.github_comments import derive_pr_state
|
||||
from ..utils.slack import get_slack_permalink
|
||||
|
|
@ -168,9 +169,18 @@ async def _record_pr_telemetry(
|
|||
)
|
||||
|
||||
|
||||
async def _build_source_references() -> str:
|
||||
"""Build a `## References` section linking the run's source (Slack/Linear)."""
|
||||
configurable = get_config().get("configurable", {})
|
||||
def _plan_reference_line(configurable: dict[str, Any]) -> str | None:
|
||||
thread_id = configurable.get("thread_id")
|
||||
if not isinstance(thread_id, str):
|
||||
return None
|
||||
plan_url = dashboard_plan_url(thread_id)
|
||||
if not plan_url:
|
||||
return None
|
||||
return f"- Plan: {plan_url}"
|
||||
|
||||
|
||||
async def _build_source_reference_lines(configurable: dict[str, Any]) -> list[str]:
|
||||
"""Build source reference lines for the run."""
|
||||
source = configurable.get("source")
|
||||
lines: list[str] = []
|
||||
|
||||
|
|
@ -191,9 +201,7 @@ async def _build_source_references() -> str:
|
|||
elif identifier:
|
||||
lines.append(f"- Linear ticket: {identifier}")
|
||||
|
||||
if not lines:
|
||||
return ""
|
||||
return _REFERENCES_HEADING + "\n" + "\n".join(lines)
|
||||
return lines
|
||||
|
||||
|
||||
async def _is_private_repo(client: httpx.AsyncClient, token: str, owner: str, repo: str) -> bool:
|
||||
|
|
@ -205,25 +213,31 @@ async def _is_private_repo(client: httpx.AsyncClient, token: str, owner: str, re
|
|||
return bool(data.get("private")) if isinstance(data, dict) else False
|
||||
|
||||
|
||||
async def _maybe_append_source_references(
|
||||
async def _maybe_append_references(
|
||||
client: httpx.AsyncClient, token: str, owner: str, repo: str, body: str
|
||||
) -> str:
|
||||
"""Append source references to the PR body for private repos only.
|
||||
|
||||
Gated to private repos so private Slack thread URLs / Linear identifiers are
|
||||
never published to a public PR.
|
||||
"""
|
||||
"""Append run references to the PR body."""
|
||||
try:
|
||||
if _REFERENCES_HEADING in body:
|
||||
return body
|
||||
references = await _build_source_references()
|
||||
if not references:
|
||||
configurable = get_config().get("configurable", {})
|
||||
if not isinstance(configurable, dict):
|
||||
configurable = {}
|
||||
lines: list[str] = []
|
||||
plan_line = _plan_reference_line(configurable)
|
||||
if plan_line:
|
||||
lines.append(plan_line)
|
||||
try:
|
||||
source_lines = await _build_source_reference_lines(configurable)
|
||||
if source_lines and await _is_private_repo(client, token, owner, repo):
|
||||
lines.extend(source_lines)
|
||||
except Exception:
|
||||
logger.debug("Failed to append source references to PR body", exc_info=True)
|
||||
if not lines:
|
||||
return body
|
||||
if not await _is_private_repo(client, token, owner, repo):
|
||||
return body
|
||||
return f"{body.rstrip()}\n\n{references}"
|
||||
return f"{body.rstrip()}\n\n{_REFERENCES_HEADING}\n" + "\n".join(lines)
|
||||
except Exception:
|
||||
logger.debug("Failed to append source references to PR body", exc_info=True)
|
||||
logger.debug("Failed to append references to PR body", exc_info=True)
|
||||
return body
|
||||
|
||||
|
||||
|
|
@ -245,7 +259,7 @@ async def _open_pull_request(
|
|||
}
|
||||
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
body = await _maybe_append_source_references(client, token, owner, repo, body)
|
||||
body = await _maybe_append_references(client, token, owner, repo, body)
|
||||
payload = {"title": title, "head": head, "base": base, "body": body, "draft": draft}
|
||||
resp = await client.post(
|
||||
f"{GITHUB_API}/repos/{owner}/{repo}/pulls",
|
||||
|
|
|
|||
|
|
@ -284,6 +284,50 @@ def test_appends_slack_reference_for_private_repo(monkeypatch: pytest.MonkeyPatc
|
|||
assert "- Slack thread: https://slack.example/p1" in sent_body
|
||||
|
||||
|
||||
def test_appends_plan_reference_from_thread_id(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("DASHBOARD_BASE_URL", "https://dashboard.example")
|
||||
_set_config(monkeypatch, {"source": "dashboard", "thread_id": "thread-1"})
|
||||
_stub_token(monkeypatch)
|
||||
|
||||
client = _FakeClient(post=_FakeResponse(201, {"html_url": "u", "number": 1, "user": {}}))
|
||||
_install_client(monkeypatch, client)
|
||||
|
||||
_open_with_body("body")
|
||||
|
||||
assert client.post_calls[0]["json"]["body"] == (
|
||||
"body\n\n## References\n- Plan: https://dashboard.example/agents/thread-1/plan"
|
||||
)
|
||||
assert client.get_calls == []
|
||||
|
||||
|
||||
def test_plan_reference_survives_source_reference_failure(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv("DASHBOARD_BASE_URL", "https://dashboard.example")
|
||||
_set_config(
|
||||
monkeypatch,
|
||||
{
|
||||
"source": "slack",
|
||||
"thread_id": "thread-1",
|
||||
"slack_thread": {"channel_id": "C123", "thread_ts": "1700000000.000100"},
|
||||
},
|
||||
)
|
||||
_stub_token(monkeypatch)
|
||||
|
||||
async def fail_permalink(*_args: Any, **_kwargs: Any) -> str:
|
||||
raise RuntimeError("slack failed")
|
||||
|
||||
monkeypatch.setattr(opr, "get_slack_permalink", fail_permalink)
|
||||
client = _FakeClient(post=_FakeResponse(201, {"html_url": "u", "number": 1, "user": {}}))
|
||||
_install_client(monkeypatch, client)
|
||||
|
||||
_open_with_body("body")
|
||||
|
||||
sent_body = client.post_calls[0]["json"]["body"]
|
||||
assert "- Plan: https://dashboard.example/agents/thread-1/plan" in sent_body
|
||||
assert client.get_calls == []
|
||||
|
||||
|
||||
def test_no_reference_for_public_repo(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_set_config(
|
||||
monkeypatch,
|
||||
|
|
@ -308,6 +352,36 @@ def test_no_reference_for_public_repo(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
assert client.post_calls[0]["json"]["body"] == "original body"
|
||||
|
||||
|
||||
def test_public_repo_appends_plan_but_not_source_reference(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setenv("DASHBOARD_BASE_URL", "https://dashboard.example")
|
||||
_set_config(
|
||||
monkeypatch,
|
||||
{
|
||||
"source": "slack",
|
||||
"thread_id": "thread-1",
|
||||
"slack_thread": {"channel_id": "C123", "thread_ts": "1700000000.000100"},
|
||||
},
|
||||
)
|
||||
_stub_token(monkeypatch)
|
||||
monkeypatch.setattr(
|
||||
opr, "get_slack_permalink", lambda *_a, **_k: _coro("https://slack.example/p1")
|
||||
)
|
||||
|
||||
client = _RoutingClient(
|
||||
post=_FakeResponse(201, {"html_url": "u", "number": 1, "user": {}}),
|
||||
get_routes={"/repos/langchain-ai/open-swe": _FakeResponse(200, {"private": False})},
|
||||
)
|
||||
_install_client(monkeypatch, client)
|
||||
|
||||
_open_with_body("body")
|
||||
|
||||
sent_body = client.post_calls[0]["json"]["body"]
|
||||
assert "- Plan: https://dashboard.example/agents/thread-1/plan" in sent_body
|
||||
assert "Slack thread" not in sent_body
|
||||
|
||||
|
||||
def test_appends_linear_reference_for_private_repo(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_set_config(
|
||||
monkeypatch,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue