mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-05 23:32:14 +00:00
feat: link PR attribution footer to the originating thread (#1539)
The "Made by Open SWE" PR footer linked to the generic dashboard homepage. Point it at the dashboard thread that generated the PR (/agents/<thread_id>), falling back to the homepage when no thread id is available. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
parent
0108d764d9
commit
554d754585
4 changed files with 61 additions and 11 deletions
|
|
@ -6,8 +6,8 @@ from pathlib import Path
|
||||||
from .utils.authorship import (
|
from .utils.authorship import (
|
||||||
OPEN_SWE_BOT_EMAIL,
|
OPEN_SWE_BOT_EMAIL,
|
||||||
OPEN_SWE_BOT_NAME,
|
OPEN_SWE_BOT_NAME,
|
||||||
PR_ATTRIBUTION_FOOTER,
|
|
||||||
CollaboratorIdentity,
|
CollaboratorIdentity,
|
||||||
|
build_pr_attribution_footer,
|
||||||
)
|
)
|
||||||
from .utils.github_comments import UNTRUSTED_GITHUB_COMMENT_OPEN_TAG
|
from .utils.github_comments import UNTRUSTED_GITHUB_COMMENT_OPEN_TAG
|
||||||
|
|
||||||
|
|
@ -375,7 +375,7 @@ This run was triggered by **{display_name}**. You author the work **as them**
|
||||||
{bot_coauthor_trailer}
|
{bot_coauthor_trailer}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **PR body**: append this line to the bottom of the PR description (separated from the body by a blank line) when you open or update the draft PR. Do not duplicate it if it is already present. If the PR body already contains a legacy footer like `_Opened collaboratively by {display_name} and open-swe._`, replace that legacy footer with this line instead of appending a second footer:
|
- **PR body**: append this line to the bottom of the PR description (separated from the body by a blank line) when you open or update the draft PR. Do not duplicate it if it is already present. If the PR body already contains a `Made by [Open SWE]` footer pointing at a different link, or a legacy footer like `_Opened collaboratively by {display_name} and open-swe._`, replace that existing footer with this line instead of appending a second footer:
|
||||||
|
|
||||||
```
|
```
|
||||||
{pr_attribution_footer}
|
{pr_attribution_footer}
|
||||||
|
|
@ -384,12 +384,15 @@ This run was triggered by **{display_name}**. You author the work **as them**
|
||||||
If you forget the trailer on a local commit that has not been pushed, fix it with `git commit --amend` before pushing — do not push without it. If the commit has already been pushed, leave it as-is and add the trailer to your next commit; never rewrite remote history to fix it."""
|
If you forget the trailer on a local commit that has not been pushed, fix it with `git commit --amend` before pushing — do not push without it. If the commit has already been pushed, leave it as-is and add the trailer to your next commit; never rewrite remote history to fix it."""
|
||||||
|
|
||||||
|
|
||||||
def _render_collaboration_section(identity: CollaboratorIdentity | None) -> str:
|
def _render_collaboration_section(
|
||||||
|
identity: CollaboratorIdentity | None,
|
||||||
|
thread_url: str | None = None,
|
||||||
|
) -> str:
|
||||||
if identity is None:
|
if identity is None:
|
||||||
return ""
|
return ""
|
||||||
return COLLABORATION_TEMPLATE.format(
|
return COLLABORATION_TEMPLATE.format(
|
||||||
display_name=identity.display_name,
|
display_name=identity.display_name,
|
||||||
pr_attribution_footer=PR_ATTRIBUTION_FOOTER,
|
pr_attribution_footer=build_pr_attribution_footer(thread_url),
|
||||||
bot_coauthor_trailer=f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>",
|
bot_coauthor_trailer=f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -446,6 +449,7 @@ def construct_system_prompt(
|
||||||
create_prs: bool = False,
|
create_prs: bool = False,
|
||||||
default_repo: dict[str, str] | None = None,
|
default_repo: dict[str, str] | None = None,
|
||||||
repo_custom_instructions: str | None = None,
|
repo_custom_instructions: str | None = None,
|
||||||
|
thread_url: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
default_prompt_section = _load_default_prompt()
|
default_prompt_section = _load_default_prompt()
|
||||||
if default_repo and default_repo.get("owner") and default_repo.get("name"):
|
if default_repo and default_repo.get("owner") and default_repo.get("name"):
|
||||||
|
|
@ -468,7 +472,7 @@ def construct_system_prompt(
|
||||||
linear_issue_number=linear_issue_number or "<ISSUE_NUMBER>",
|
linear_issue_number=linear_issue_number or "<ISSUE_NUMBER>",
|
||||||
default_prompt_section=default_prompt_section,
|
default_prompt_section=default_prompt_section,
|
||||||
pr_policy_override_section=ALWAYS_CREATE_PR_SECTION if create_prs else "",
|
pr_policy_override_section=ALWAYS_CREATE_PR_SECTION if create_prs else "",
|
||||||
collaboration_section=_render_collaboration_section(triggering_user_identity),
|
collaboration_section=_render_collaboration_section(triggering_user_identity, thread_url),
|
||||||
repo_instructions_section=_render_repo_instructions_section(repo_custom_instructions),
|
repo_instructions_section=_render_repo_instructions_section(repo_custom_instructions),
|
||||||
commit_identity_name=commit_identity_name,
|
commit_identity_name=commit_identity_name,
|
||||||
commit_identity_email=commit_identity_email,
|
commit_identity_email=commit_identity_email,
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ from .utils.authorship import (
|
||||||
OPEN_SWE_BOT_NAME,
|
OPEN_SWE_BOT_NAME,
|
||||||
resolve_triggering_user_identity,
|
resolve_triggering_user_identity,
|
||||||
)
|
)
|
||||||
|
from .utils.dashboard_links import dashboard_thread_url
|
||||||
from .utils.github_app import (
|
from .utils.github_app import (
|
||||||
get_github_app_installation_token_with_expiry,
|
get_github_app_installation_token_with_expiry,
|
||||||
)
|
)
|
||||||
|
|
@ -691,6 +692,7 @@ async def get_agent(config: RunnableConfig) -> Pregel:
|
||||||
create_prs=always_create_prs,
|
create_prs=always_create_prs,
|
||||||
default_repo=prompt_default_repo,
|
default_repo=prompt_default_repo,
|
||||||
repo_custom_instructions=repo_custom_instructions,
|
repo_custom_instructions=repo_custom_instructions,
|
||||||
|
thread_url=dashboard_thread_url(thread_id),
|
||||||
),
|
),
|
||||||
tools=[
|
tools=[
|
||||||
http_request,
|
http_request,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,15 @@ OPEN_SWE_BOT_NAME = "open-swe[bot]"
|
||||||
# accepts, which broke preview deploys on commits carrying this co-author.
|
# accepts, which broke preview deploys on commits carrying this co-author.
|
||||||
OPEN_SWE_BOT_EMAIL = "open-swe@users.noreply.github.com"
|
OPEN_SWE_BOT_EMAIL = "open-swe@users.noreply.github.com"
|
||||||
|
|
||||||
PR_ATTRIBUTION_FOOTER = "Made by [Open SWE](https://openswe.vercel.app)"
|
PR_ATTRIBUTION_TEXT = "Made by [Open SWE]"
|
||||||
|
PR_ATTRIBUTION_DEFAULT_URL = "https://openswe.vercel.app"
|
||||||
|
PR_ATTRIBUTION_FOOTER = f"{PR_ATTRIBUTION_TEXT}({PR_ATTRIBUTION_DEFAULT_URL})"
|
||||||
|
|
||||||
|
|
||||||
|
def build_pr_attribution_footer(thread_url: str | None = None) -> str:
|
||||||
|
"""Build the Open SWE PR footer, linking the run's thread when available."""
|
||||||
|
url = thread_url.strip() if isinstance(thread_url, str) and thread_url.strip() else ""
|
||||||
|
return f"{PR_ATTRIBUTION_TEXT}({url or PR_ATTRIBUTION_DEFAULT_URL})"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|
@ -159,17 +167,21 @@ def add_bot_coauthor_trailer(commit_message: str) -> str:
|
||||||
def add_pr_collaboration_note(
|
def add_pr_collaboration_note(
|
||||||
pr_body: str,
|
pr_body: str,
|
||||||
identity: CollaboratorIdentity | None = None,
|
identity: CollaboratorIdentity | None = None,
|
||||||
|
thread_url: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Append the Open SWE attribution footer to a PR body.
|
"""Append the Open SWE attribution footer to a PR body.
|
||||||
|
|
||||||
The PR is opened as the triggering user, so the body only credits Open SWE
|
The PR is opened as the triggering user, so the body only credits Open SWE
|
||||||
as the collaborator. Any legacy double-attribution footer is replaced.
|
as the collaborator. The footer links the run's thread when available. Any
|
||||||
|
legacy double-attribution footer is replaced.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
normalized_body = pr_body.rstrip()
|
normalized_body = pr_body.rstrip()
|
||||||
note = PR_ATTRIBUTION_FOOTER
|
note = build_pr_attribution_footer(thread_url)
|
||||||
if note in normalized_body:
|
if note in normalized_body:
|
||||||
return normalized_body
|
return normalized_body
|
||||||
|
if PR_ATTRIBUTION_TEXT in normalized_body:
|
||||||
|
return normalized_body
|
||||||
|
|
||||||
legacy_footers: list[str] = []
|
legacy_footers: list[str] = []
|
||||||
if identity is not None:
|
if identity is not None:
|
||||||
|
|
|
||||||
|
|
@ -126,12 +126,27 @@ def test_construct_system_prompt_includes_github_login_in_pr_footer() -> None:
|
||||||
assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt
|
assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt
|
||||||
assert _BOT_TRAILER in prompt
|
assert _BOT_TRAILER in prompt
|
||||||
assert "Made by [Open SWE](https://openswe.vercel.app)" in prompt
|
assert "Made by [Open SWE](https://openswe.vercel.app)" in prompt
|
||||||
assert (
|
assert "replace that existing footer with this line" in prompt
|
||||||
"replace that legacy footer with this line instead of appending a second footer" in prompt
|
|
||||||
)
|
|
||||||
assert "`_Opened collaboratively by Mona Lisa and open-swe._`" in prompt
|
assert "`_Opened collaboratively by Mona Lisa and open-swe._`" in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_construct_system_prompt_footer_links_thread_when_provided() -> None:
|
||||||
|
identity = CollaboratorIdentity(
|
||||||
|
display_name="octocat",
|
||||||
|
commit_name="octocat",
|
||||||
|
commit_email="1234+octocat@users.noreply.github.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt = construct_system_prompt(
|
||||||
|
working_dir="/workspace",
|
||||||
|
triggering_user_identity=identity,
|
||||||
|
thread_url="https://openswe.vercel.app/agents/abc-123",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "Made by [Open SWE](https://openswe.vercel.app/agents/abc-123)" in prompt
|
||||||
|
assert "Made by [Open SWE](https://openswe.vercel.app)" not in prompt
|
||||||
|
|
||||||
|
|
||||||
def test_construct_system_prompt_shell_escapes_user_name() -> None:
|
def test_construct_system_prompt_shell_escapes_user_name() -> None:
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
|
|
@ -168,6 +183,23 @@ def test_add_pr_collaboration_note_replaces_legacy_footer() -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_pr_collaboration_note_links_thread() -> None:
|
||||||
|
body = "## Description\nDone."
|
||||||
|
|
||||||
|
assert add_pr_collaboration_note(
|
||||||
|
body, thread_url="https://openswe.vercel.app/agents/abc-123"
|
||||||
|
) == ("## Description\nDone.\n\nMade by [Open SWE](https://openswe.vercel.app/agents/abc-123)")
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_pr_collaboration_note_skips_when_footer_present_with_other_link() -> None:
|
||||||
|
body = "## Description\nDone.\n\nMade by [Open SWE](https://openswe.vercel.app)"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
add_pr_collaboration_note(body, thread_url="https://openswe.vercel.app/agents/abc-123")
|
||||||
|
== body
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_triggering_user_identity_combines_slack_name_with_github_login() -> None:
|
def test_resolve_triggering_user_identity_combines_slack_name_with_github_login() -> None:
|
||||||
identity = resolve_triggering_user_identity(
|
identity = resolve_triggering_user_identity(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue