Merge branch 'dev' into feature/secrets-id-list-scoping

This commit is contained in:
Adam Moussa 2026-06-28 20:41:57 -04:00 • committed by GitHub
commit 9f47060854
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 208 additions and 9635 deletions

View file

@ -1,52 +1,45 @@
version: 2
updates:
# Python — uv (open-swe-specific; siblings use pip)
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "monthly"
interval: "weekly"
assignees: ["amoussa1229"]
groups:
minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
major:
patterns:
- "*"
update-types:
- "major"
update-types: ["minor", "patch"]
# JavaScript/TypeScript — CDK (/infra), Playwright (/tests/e2e), dashboard (/ui), root tooling
- package-ecosystem: "npm"
directories:
- "/"
- "/infra"
- "/tests/e2e"
- "/ui"
schedule:
interval: "weekly"
assignees: ["amoussa1229"]
groups:
minor-and-patch:
update-types: ["minor", "patch"]
# Docker — root Dockerfile
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "monthly"
interval: "weekly"
assignees: ["amoussa1229"]
groups:
minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
major:
patterns:
- "*"
update-types:
- "major"
update-types: ["minor", "patch"]
# GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
interval: "weekly"
assignees: ["amoussa1229"]
groups:
minor-and-patch:
patterns:
- "*"
update-types:
- "minor"
- "patch"
major:
patterns:
- "*"
update-types:
- "major"
update-types: ["minor", "patch"]

View file

@ -4,7 +4,7 @@
# Reads check-runs on stdin — one
# name<US>status<US>conclusion<US>details_url
# per line, fields separated by ASCII Unit Separator (0x1F) — so it is unit-testable
# WITHOUT GitHub. promote_dev_to_prod.yml pipes the live `gh api .../check-runs`
# WITHOUT GitHub. promote-dev-to-prod.yml pipes the live `gh api .../check-runs`
# output in. 0x1F (not TAB) is used deliberately: TAB is IFS-whitespace, so an empty
# conclusion (every in_progress check has a null conclusion) would collapse and shift
# the columns — which would make the promote run fail to exclude itself. 0x1F is
@ -26,12 +26,12 @@
set -euo pipefail
EXCLUDE_RUN_ID="${EXCLUDE_RUN_ID:-}"
# Mandatory checks (one per line). Defaults to the Agent CI suite, which runs on
# Mandatory checks (one per line). Defaults to the CI suite, which runs on
# every push to dev (see ci.yml). Keep in sync with those job names; if a name
# drifts the gate blocks (fails safe) until the list is updated.
REQUIRED_CHECKS="${REQUIRED_CHECKS:-Agent lint
Agent format check
Agent unit tests
REQUIRED_CHECKS="${REQUIRED_CHECKS:-Lint
Format check
Unit tests
Playwright E2E}"
declare -A GREEN

View file

@ -62,7 +62,7 @@ jobs:
BUCKET: open-swe-dev-assets
DEPLOY_DOC: open-swe-dev-deploy
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
@ -100,7 +100,7 @@ jobs:
BUCKET: open-swe-prod-assets
DEPLOY_DOC: open-swe-prod-deploy
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

View file

@ -66,7 +66,7 @@ jobs:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v4
with:
node-version: "24"
@ -102,7 +102,7 @@ jobs:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v4
with:
node-version: "24"

View file

@ -1,6 +1,6 @@
name: Infra CI
# Path-filtered CI for the /infra CDK app (TypeScript). The existing "Agent CI"
# Path-filtered CI for the /infra CDK app (TypeScript). The existing "CI"
# (ci.yml) covers the Python agent; this adds tsc + jest + cdk synth for /infra so
# infra changes are gated on a PR the same way. Runs only when /infra changes.

View file

@ -1,11 +1,11 @@
name: Agent CI
name: CI
permissions:
contents: read
on:
push:
# dev as well as main so every dev HEAD carries the full Agent CI signal that
# dev as well as main so every dev HEAD carries the full CI signal that
# the dev->main promotion gate (check-dev-green.sh) reads. PR checks alone are
# not enough: an admin-merge can land a red PR onto dev.
branches: ["main", "dev"]
@ -18,33 +18,33 @@ concurrency:
jobs:
lint:
name: Agent lint
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: Install dependencies
run: uv sync --locked --extra dev
- name: Run lint
run: make lint
format:
name: Agent format check
name: Format check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: Install dependencies
run: uv sync --locked --extra dev
- name: Run format check
run: make format-check
unit-tests:
name: Agent unit tests
name: Unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: Install dependencies
run: uv sync --locked --extra dev
- name: Run unit tests
@ -55,8 +55,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- uses: actions/setup-node@v4
with:
node-version: 22

15
.github/workflows/dependency-review.yml vendored Normal file
View file

@ -0,0 +1,15 @@
name: Dependency Review
on: pull_request
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high

13
.github/workflows/labeler.yml vendored Normal file
View file

@ -0,0 +1,13 @@
name: Labeler
on:
pull_request:
permissions:
contents: read
pull-requests: write
issues: write
jobs:
labeler:
uses: Sea-Haven-Industries/.github/.github/workflows/callable-labeler.yaml@main

View file

@ -19,7 +19,7 @@ jobs:
contents: write
checks: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
ref: dev
fetch-depth: 0

View file

@ -78,8 +78,8 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 360
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: Install dependencies
run: uv sync --locked
- name: Run reviewer eval

View file

@ -50,7 +50,7 @@ jobs:
DEPLOY_DOC: open-swe-dev-deploy
TARGET_SHA: ${{ inputs.sha }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_APP_DEV }}
@ -75,7 +75,7 @@ jobs:
DEPLOY_DOC: open-swe-prod-deploy
TARGET_SHA: ${{ inputs.sha }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_APP_PROD }}

View file

@ -1,4 +1,4 @@
FROM python:3.14.5-slim-trixie
FROM python:3.14.6-slim-trixie
ARG DOCKER_CLI_VERSION=5:29.1.5-1~debian.13~trixie
ARG NODEJS_VERSION=22.22.0-1nodesource1

View file

@ -108,6 +108,10 @@ All three companies in the article converge on **Slack as the primary invocation
Each invocation creates a deterministic thread ID, so follow-up messages on the same issue or thread route to the same running agent.
**Trigger tags (Sea Haven fork):** a mention is a case-insensitive substring match on the comment body — `@openswe`, `@open-swe`, `@openswe-dev`, or `@seahaven-openswe` (the deployed App slug). GitHub won't linkify `@seahaven-openswe` (App `[bot]` accounts aren't user-mentionable), but the text still fires a run.
**Engineering conventions & attribution (Sea Haven fork):** the main agent's system prompt is tuned to the Sea Haven engineering handbook — branch names are `feature|bug|hotfix/<kebab-desc>` (optional resolvable `<KEY>-` prefix), PR bodies use `## Summary / Validation / Tests / Notes`, and commit messages follow the handbook format (≤50-char imperative subject, *why* over *what*). The **PR title rule is repo-aware**: when the target repo enforces a conventional-commit title (an `amannn/action-semantic-pull-request` workflow, a `commitlint` config, or a documented requirement in `AGENTS.md` / `CONTRIBUTING.md`), the agent emits a conforming `type(scope): …` title that reads the action's allowed types/scopes — this lets it pass gates like this repo's own `PR Title Lint` and upstream `langchain-ai/open-swe` without manual retitling; otherwise it falls back to the Sea Haven imperative style with no `type:` prefix. PRs that resolve a GitHub issue **auto-link it** in the body (`Closes #<n>` for full fixes, `Refs #<n>`/`Part of #<n>` for partial work, `Closes owner/repo#<n>` cross-repo); because the Sea Haven flow targets `dev` rather than the default branch, the issue closes when `dev` is promoted, not at dev-merge. **No agent/AI attribution is added to any artifact** — no `Co-authored-by` bot trailer, no `Made by [Open SWE]` footer, no "generated by an agent" notes. Commits are currently authored as the **triggering user** (the upstream behavior, which keeps Vercel preview deploys resolvable); flipping authorship to the bot account is tracked separately in issue #11 pending the Vercel-resolvability decision.
### 7. Validation — Prompt-Driven
The agent is instructed to run linters, formatters, and tests before committing, and is responsible end-to-end for committing, pushing, opening/updating the draft PR, and replying in the source channel.

View file

@ -7,7 +7,6 @@ from .utils.authorship import (
OPEN_SWE_BOT_EMAIL,
OPEN_SWE_BOT_NAME,
CollaboratorIdentity,
build_pr_attribution_footer,
)
from .utils.github_comments import UNTRUSTED_GITHUB_COMMENT_OPEN_TAG
@ -159,7 +158,12 @@ Before starting any task that requires code changes, set up the repository in yo
This sets the author of every commit you make. This is required for CI: third-party integrations (e.g. Vercel preview deploys) reject commits whose author email cannot be resolved to a GitHub account, and this email resolves. Do NOT set any other identity, do NOT pass `--author` to `git commit`, and do NOT export `GIT_AUTHOR_*` / `GIT_COMMITTER_*` env vars.
4. **Choose your branch** — Use a thread-stable branch name such as `open-swe/<short-task-slug>`. If a branch already exists for this thread/task, fetch and check it out instead of creating a new one.
4. **Choose your branch** — Use a Sea Haven branch name: `<prefix>/<description>`, all kebab-case. Pick the prefix by the kind of work:
- `feature/` — new functionality or an enhancement
- `bug/` — a defect caught before it reaches production
- `hotfix/` — a fix for a production-impacting issue
Keep `<description>` short and kebab-case (e.g. `feature/add-receipt-parser`). When a ticket key is resolvable from the run context, put it first: `feature/<KEY>-add-receipt-parser`; if no key is resolvable, omit it. Never commit directly to `main`. Keep the branch thread-stable: if a branch already exists for this thread/task, fetch and check it out instead of creating a new one.
5. **Checkout your branch** — Always fetch and checkout your branch before making any changes. When reusing an existing remote branch, start from `origin/<branch>` rather than recreating the branch from the base branch; this preserves prior commits for review.
@ -390,31 +394,55 @@ When you have completed your implementation, follow these steps in order:
- **Open a new PR** with the `open_pull_request` tool (pass `owner`, `repo`, `head` = your branch, `base`, `title`, `body`). This attributes the PR to the triggering user. Push the branch BEFORE calling it.
- **Update an existing PR** (edit the body, mark ready for review, etc.) with `GH_TOKEN=dummy gh pr edit`. If a PR already exists for the branch (including one the user pasted in), do NOT open a duplicate — `open_pull_request` returns the existing PR's URL, so switch to `gh pr edit`. For follow-up changes, add a new commit on top of the existing branch history.
**PR Title** (under 70 characters):
```
<type>: <concise description> [closes <TICKET>]
```
Where type is one of: `fix` (bug fix), `feat` (new feature), `chore` (maintenance), `ci` (CI/CD).
Always append the resolvable ticket number in square brackets at the end of the title (e.g. `fix: handle null session [closes AB-000]`). Resolve the ticket from the Linear-triggered run when present (`{linear_project_id}-{linear_issue_number}`), or from a Linear ticket referenced in the Slack thread / task context. If no ticket number is resolvable, omit the bracketed suffix entirely.
**PR Title** (under 70 characters): the title rule is **repo-aware** — first detect whether the target repo enforces a conventional-commit PR title, then pick the matching style. The repo is already cloned, so this check is cheap.
**PR Body** (keep under 10 lines total. the more concise the better):
```
## Description
<1-3 sentences on WHY and the approach.
NO "Changes:" section — file changes are already in the commit history.>
*Detect a conventional-commit title gate* — the repo enforces one if ANY of these hold:
- a workflow under `.github/workflows/` references `amannn/action-semantic-pull-request` (or any `semantic-pull-request` action);
- a `commitlint` config wired to PR titles (`commitlint.config.*`, `.commitlintrc*`, or a `commitlint` key in `package.json`);
- `AGENTS.md` / `CONTRIBUTING.md` states a conventional-commit title requirement.
## Release Note
<One-line changelog summary for self-hosted customers, or "none" for internal/CI/test/refactor changes.>
*If a gate is enforced* → emit a conventional-commit title `type(scope): description` and conform to the action's configuration. This **overrides** the Sea Haven no-`type:`-prefix default. Open the workflow (e.g. `.github/workflows/pr_lint.yml`) and read the allowed `types`/`scopes` so you stay inside them; if `requireScope` is false, a scope is optional. Map the work to a type: new functionality → `feat`, defect fix → `fix`, infra/CI → `ci`/`build`/`chore`, docs → `docs`, tests → `test`, refactor → `refactor`, perf → `perf`. Examples: `feat: add retry logic for transient upstream failures` or `fix(deps): pin langgraph-cli`. Do NOT rely on an escape-hatch label (e.g. `ignore-lint-pr-title`) to dodge the check — conform to the title instead. (Note: this repo's own `PR Title Lint` and upstream `langchain-ai/open-swe` both enforce this — emit a conforming `type:` title for them.)
## Test Plan
- [ ] <new/novel verification steps only — NOT "run existing tests" or "verify existing behavior">
*If no gate is enforced* → use the Sea Haven imperative style: imperative mood, capitalized, describing the change — not the ticket. Do NOT use a conventional-commit `type:` prefix (no `feat:`/`fix:`/`chore:`). When a ticket key is resolvable from the run context, prefix it in square brackets; otherwise omit it entirely:
```
[<KEY>] Add retry logic for transient upstream failures
```
With no resolvable key, use just the imperative description: `Add retry logic for transient upstream failures`. Resolve the key from the Linear-triggered run when present (`{linear_project_id}-{linear_issue_number}`), or from a Linear ticket referenced in the Slack thread / task context.
**PR Body** — use this structure. Omit a section only when it would be empty:
```
## Summary
<What changed and why — 1-3 sentences. Explain the motivation, not just the diff.>
## Validation
<How you verified it works — commands run, steps taken, screenshots if UI.>
## Tests
<What tests were added, updated, or run. If no automated tests, explain manual testing.>
## Notes
<Anything reviewers should know — migration steps, deploy order, follow-ups, breaking changes. Omit this section if empty.>
```
**Link the GitHub issue the PR resolves** — when the run originates from (or fully fixes) a GitHub issue, add a closing keyword to the PR body so merging auto-closes the issue. The issue number is usually in-context: issue-triggered runs receive a `## GitHub Issue: #<n>` line; for Slack/Linear-triggered runs that fix a GitHub issue, pick `#<n>` up from the task text.
- When the PR **fully resolves** a GitHub issue in the **same repo**, add a dedicated trailing line in `## Summary` (or its own line at the end of the body): `Closes #<n>`. GitHub recognizes `Closes`/`Fixes`/`Resolves #<n>` anywhere in the body.
- When the PR only **partially** addresses an issue (more work remains), use a **non-closing** reference so the issue stays open: `Refs #<n>` or `Part of #<n>`.
- **Cross-repo**: if the issue lives in a different repo, use the fully-qualified form: `Closes owner/repo#<n>` (or `Refs owner/repo#<n>` for partial).
- This is the GitHub-issue analog of the Linear `Refs: <KEY>` commit trailer — placed in the PR body where GitHub's auto-close looks.
- **Default-branch caveat (don't mistake this for a bug):** GitHub only auto-closes the linked issue when the PR merges into the repo's **default branch**. In the Sea Haven flow the agent targets `dev`, not the default branch, so `Closes #<n>` will **not** close the issue at dev-merge time — it closes when `dev` is promoted to the default branch. The link still renders, and the issue closes on promotion; this is the correct, expected outcome. On repos where the agent targets the default branch directly, it closes on merge as usual.
You don't need to add links back to the originating Slack thread or Linear ticket — for private repos, `open_pull_request` appends a `## References` section automatically.
When the target repo is public, don't reference private repos or private PR/issue numbers in the description.
**Commit message**: Concise, focusing on the "why" rather than the "what". If not provided, the PR title is used.
**Commit message** — follow the Sea Haven format:
- Imperative mood, capitalized first letter (e.g. "Add retry logic", not "Added retry logic" or "adds retry logic").
- Subject line ≤50 characters. If you need more, add a blank line and a body wrapped at 72 characters.
- Explain *why*, not *what* — the diff already shows what changed.
- No generic subjects ("Fix stuff", "Update code", "WIP", "Address review comments") and no self-referential phrasing ("This commit…", "This PR…", "I refactored…").
- When a ticket key is resolvable, add a `Refs: <KEY>` trailer (combine with `#<issue>` when both apply); otherwise omit the trailer.
This per-commit convention is independent of the repo-aware **PR title** rule above. On a repo that requires conventional PR titles **and** squash-merges, the squash commit subject becomes the PR title (e.g. `feat: …`) and so diverges from this imperative-no-prefix commit style — that's an acceptable tradeoff (the target repo's title lint wins), not a contradiction. Your own per-commit subjects still follow the Sea Haven format here.
**IMPORTANT: For code-change tasks, never ask the user for permission or confirmation before pushing commits or opening/updating a draft PR. Do not say "if you want, I can proceed" or "shall I open the PR?". When implementation is done and checks pass, push autonomously, and open/update a draft PR autonomously when requested, necessary, or required by the Always Create PRs dashboard setting.**
@ -448,23 +476,17 @@ For code-change tasks, push the branch and notify the appropriate source once im
COLLABORATION_TEMPLATE = """---
### Collaborative Attribution
### Authorship & Attribution
This run was triggered by **{display_name}**. You author the work **as them** — their git identity is already configured in the Repository Setup step, so every commit and the PR are attributed to them. Credit open-swe as the collaborator:
This run was triggered by **{display_name}**. You author the work as them — their git identity is already configured in the Repository Setup step, so every commit and the PR are attributed to them.
- **Commits**: append this trailer (verbatim, on its own line, separated from the message body by a blank line) to every commit message you author. Add it to both the first commit and any follow-up commits in this run:
**Add NO agent or AI attribution to any artifact.** Sea Haven artifacts carry no agent attribution — write commits, PR descriptions, and comments exactly as a human engineer would. Specifically, never add:
```
{bot_coauthor_trailer}
```
- A `Co-authored-by:` trailer for any bot or agent (no `open-swe[bot]`, no `Claude`, etc.).
- A PR-body footer or tagline such as `Made by [Open SWE]`, `Generated with …`, a 🤖 emoji line, or `_Opened collaboratively by … and open-swe._`.
- Any "created/opened by an agent" note in commits, PR bodies, or issue comments.
- **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}
```
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 a template or a prior artifact already contains such attribution, strip it rather than carrying it forward."""
def _render_collaboration_section(
@ -473,11 +495,7 @@ def _render_collaboration_section(
) -> str:
if identity is None:
return ""
return COLLABORATION_TEMPLATE.format(
display_name=identity.display_name,
pr_attribution_footer=build_pr_attribution_footer(thread_url),
bot_coauthor_trailer=f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>",
)
return COLLABORATION_TEMPLATE.format(display_name=identity.display_name)
ALWAYS_CREATE_PR_SECTION = """---

View file

@ -1,4 +1,4 @@
"""Helpers for collaborative commit and PR attribution."""
"""Helpers for resolving the triggering user's git identity."""
from __future__ import annotations
@ -16,16 +16,6 @@ OPEN_SWE_BOT_NAME = "open-swe[bot]"
# accepts, which broke preview deploys on commits carrying this co-author.
OPEN_SWE_BOT_EMAIL = "open-swe@users.noreply.github.com"
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)
class CollaboratorIdentity:
@ -149,50 +139,3 @@ def resolve_triggering_user_identity(
"""
return _identity_from_github_token(github_token) or _identity_from_config(config)
def add_bot_coauthor_trailer(commit_message: str) -> str:
"""Append the open-swe[bot] Co-authored-by trailer.
Commits are authored by the triggering user (via the repo-local git
identity); open-swe[bot] is credited as the collaborator.
"""
normalized_message = commit_message.rstrip()
trailer = f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>"
if trailer in normalized_message:
return normalized_message
return f"{normalized_message}\n\n{trailer}"
def add_pr_collaboration_note(
pr_body: str,
identity: CollaboratorIdentity | None = None,
thread_url: str | None = None,
) -> str:
"""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
as the collaborator. The footer links the run's thread when available. Any
legacy double-attribution footer is replaced.
"""
normalized_body = pr_body.rstrip()
note = build_pr_attribution_footer(thread_url)
if note in normalized_body:
return normalized_body
if PR_ATTRIBUTION_TEXT in normalized_body:
return normalized_body
legacy_footers: list[str] = []
if identity is not None:
legacy_footers.append(
f"_Opened collaboratively by {identity.pr_attribution_name} and open-swe._"
)
legacy_footers.append(f"_Opened collaboratively by {identity.display_name} and open-swe._")
for legacy in legacy_footers:
if legacy in normalized_body:
return normalized_body.replace(legacy, note)
if not normalized_body:
return note
return f"{normalized_body}\n\n{note}"

View file

@ -31,7 +31,7 @@ __all__ = [
"verify_github_signature",
]
OPEN_SWE_TAGS = ("@openswe", "@open-swe", "@openswe-dev")
OPEN_SWE_TAGS = ("@openswe", "@open-swe", "@openswe-dev", "@seahaven-openswe")
UNTRUSTED_GITHUB_COMMENT_OPEN_TAG = "<dangerous-external-untrusted-users-comment>"
UNTRUSTED_GITHUB_COMMENT_CLOSE_TAG = "</dangerous-external-untrusted-users-comment>"
_SANITIZED_UNTRUSTED_GITHUB_COMMENT_OPEN_TAG = "[blocked-untrusted-comment-tag-open]"

View file

@ -57,8 +57,8 @@ uv run python -m evals.reviewer.run_eval --limit 3
### From the GitHub Action (recommended for full runs)
Trigger the **Reviewer eval** workflow (`.github/workflows/reviewer_eval.yml`)
from the Actions UI or `gh workflow run reviewer_eval.yml --ref prod -f limit=3`.
Trigger the **Reviewer eval** workflow (`.github/workflows/reviewer-eval.yml`)
from the Actions UI or `gh workflow run reviewer-eval.yml --ref prod -f limit=3`.
Run it on the **prod** branch so the harness/judge match the deployed reviewer it
scores. Running it on a durable runner (instead of inside the serving deployment)
means a deploy or container recycle can't kill a long run.

View file

@ -245,7 +245,7 @@ npm test # jest — naming Aspect
## CI/CD (T18 — `.github/workflows/ci-infra.yml` + `cd-infra.yml`)
Path-filtered, OIDC-only (no static keys). The Python agent keeps its own
`ci.yml` ("Agent CI"); these two add the `/infra` half.
`ci.yml` ("CI"); these two add the `/infra` half.
| Workflow | Trigger | Does |
|---|---|---|

View file

@ -10,14 +10,14 @@ dependencies = [
"fastapi>=0.136.3",
"uvicorn>=0.48.0",
"httpx>=0.28.1",
"PyJWT>=2.12.1",
"PyJWT>=2.13.0",
"cryptography>=48.0.1",
"langgraph-sdk>=0.4.2",
"langchain>=1.3.9",
"langgraph>=1.1.10",
"markdownify>=1.2.2",
"langchain-anthropic>=1.4.6",
"langgraph-cli[inmem]>=0.4.27",
"langgraph-cli[inmem]>=0.4.30",
"langsmith==0.8.18",
"langchain-openai>=1.2.2",
"langchain-fireworks>=1.4.2",

View file

@ -1,23 +1,6 @@
from __future__ import annotations
from agent.utils.authorship import (
OPEN_SWE_BOT_EMAIL,
OPEN_SWE_BOT_NAME,
add_bot_coauthor_trailer,
resolve_triggering_user_identity,
)
_BOT_TRAILER = f"Co-authored-by: {OPEN_SWE_BOT_NAME} <{OPEN_SWE_BOT_EMAIL}>"
def test_add_bot_coauthor_trailer_appends_bot() -> None:
result = add_bot_coauthor_trailer("fix: thing")
assert result == f"fix: thing\n\n{_BOT_TRAILER}"
def test_add_bot_coauthor_trailer_is_idempotent() -> None:
once = add_bot_coauthor_trailer("fix: thing")
assert add_bot_coauthor_trailer(once) == once
from agent.utils.authorship import resolve_triggering_user_identity
def test_resolve_identity_from_config_uses_user_noreply_email() -> None:

View file

@ -8,7 +8,6 @@ from agent.utils.authorship import (
OPEN_SWE_BOT_EMAIL,
OPEN_SWE_BOT_NAME,
CollaboratorIdentity,
add_pr_collaboration_note,
resolve_triggering_user_identity,
)
@ -100,7 +99,7 @@ def test_construct_system_prompt_includes_corridor_prompt_when_enabled() -> None
def test_construct_system_prompt_omits_collaboration_section_without_identity() -> None:
prompt = construct_system_prompt(working_dir="/workspace")
assert "Collaborative Attribution" not in prompt
assert "Authorship & Attribution" not in prompt
assert "Co-authored-by:" not in prompt
@ -136,7 +135,7 @@ def test_construct_system_prompt_forbids_force_push() -> None:
assert "git pull --rebase origin <branch>" in prompt
def test_construct_system_prompt_includes_coauthor_trailer_when_identity_present() -> None:
def test_construct_system_prompt_emits_no_attribution_when_identity_present() -> None:
identity = CollaboratorIdentity(
display_name="octocat",
commit_name="octocat",
@ -148,16 +147,24 @@ def test_construct_system_prompt_includes_coauthor_trailer_when_identity_present
triggering_user_identity=identity,
)
assert "Collaborative Attribution" in prompt
# The user authors the commits; open-swe[bot] is the co-author/collaborator.
# The Sea Haven Authorship section renders, the commits are still authored as
# the triggering user (identity flip to the bot is deferred — see issue #11),
# and NO agent/AI attribution leaks into the prompt.
assert "Authorship & Attribution" in prompt
assert "Add NO agent or AI attribution" in prompt
# Values are shell-escaped via shlex.quote; safe tokens need no quoting.
assert "git config user.name octocat" in prompt
assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt
assert _BOT_TRAILER in prompt
assert "Made by [Open SWE](https://openswe.vercel.app)" in prompt
# The concrete attribution artifacts the old template INSTRUCTED are gone (the
# prohibition still names them as examples, so assert the instructional forms:
# the full co-author trailer, the URL-bearing footer, and the old mandate text).
assert _BOT_TRAILER not in prompt
assert "Made by [Open SWE](https://openswe.vercel.app)" not in prompt
assert "Credit open-swe as the collaborator" not in prompt
assert "append this trailer" not in prompt
def test_construct_system_prompt_includes_github_login_in_pr_footer() -> None:
def test_construct_system_prompt_no_attribution_with_github_login() -> None:
identity = CollaboratorIdentity(
display_name="Mona Lisa",
commit_name="Mona Lisa",
@ -173,27 +180,42 @@ def test_construct_system_prompt_includes_github_login_in_pr_footer() -> None:
# A name with a space is shlex-quoted; the safe email is left bare.
assert "git config user.name 'Mona Lisa'" in prompt
assert "git config user.email 1234+octocat@users.noreply.github.com" in prompt
assert _BOT_TRAILER in prompt
assert "Made by [Open SWE](https://openswe.vercel.app)" in prompt
assert "replace that existing footer with this line" 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 _BOT_TRAILER not in prompt
assert "Made by [Open SWE](https://openswe.vercel.app)" not in prompt
assert "replace that existing footer with this line" not in prompt
def test_construct_system_prompt_uses_sea_haven_conventions() -> None:
prompt = construct_system_prompt(working_dir="/workspace")
# Branch naming, PR structure, and commit format follow the handbook.
assert "feature/" in prompt and "hotfix/" in prompt
assert "Do NOT use a conventional-commit `type:` prefix" in prompt
assert "## Summary" in prompt and "## Validation" in prompt
assert "## Release Note" not in prompt
def test_construct_system_prompt_links_resolved_github_issue() -> None:
prompt = construct_system_prompt(working_dir="/workspace")
# Part A: PRs that resolve a GitHub issue auto-link it for auto-close.
assert "Closes #<n>" in prompt
assert "Refs #<n>" in prompt or "Part of #<n>" in prompt
assert "Closes owner/repo#<n>" in prompt
# The default-branch auto-close caveat must be stated so it isn't read as a bug.
assert "default branch" in prompt
assert "promoted" in prompt or "promotion" in prompt
def test_construct_system_prompt_pr_title_rule_is_repo_aware() -> None:
prompt = construct_system_prompt(working_dir="/workspace")
# Part B: detect a conventional-commit title gate and conform to it...
assert "amannn/action-semantic-pull-request" in prompt
assert "repo-aware" in prompt
assert "type(scope): description" in prompt or "type(scope): …" in prompt
# ...without dropping the no-prefix default for repos that don't enforce one.
assert "Do NOT use a conventional-commit `type:` prefix" in prompt
def test_construct_system_prompt_shell_escapes_user_name() -> None:
@ -217,38 +239,6 @@ def test_construct_system_prompt_shell_escapes_user_name() -> None:
assert f"git config user.name {hostile}" not in prompt
def test_add_pr_collaboration_note_replaces_legacy_footer() -> None:
identity = CollaboratorIdentity(
display_name="Mona Lisa",
commit_name="Mona Lisa",
commit_email="1234+octocat@users.noreply.github.com",
github_login="octocat",
)
body = "## Description\nDone.\n\n_Opened collaboratively by Mona Lisa and open-swe._"
assert add_pr_collaboration_note(body, identity) == (
"## Description\nDone.\n\nMade by [Open SWE](https://openswe.vercel.app)"
)
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:
identity = resolve_triggering_user_identity(
{

File diff suppressed because it is too large Load diff

18
uv.lock generated
View file

@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 2
requires-python = ">=3.11"
resolution-markers = [
"python_full_version >= '3.14'",
@ -1627,7 +1627,7 @@ wheels = [
[[package]]
name = "langgraph-cli"
version = "0.4.27"
version = "0.4.30"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
@ -1636,9 +1636,9 @@ dependencies = [
{ name = "pathspec" },
{ name = "python-dotenv" },
]
sdist = { url = "https://files.pythonhosted.org/packages/02/21/046b2345d83427a59ebdac797fbe343bb8d1e575560667846f7346733774/langgraph_cli-0.4.27.tar.gz", hash = "sha256:67a64b67dddc8c670d77cc4c9663a7f8e924eaeb8857a926e87f239b699ef8f6", size = 1045499, upload-time = "2026-05-28T14:25:35.13Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f0/27/4b6a0f00c804f0b0831f741c0607b46a4cbddff14d1eab6bbd4ce5820837/langgraph_cli-0.4.30.tar.gz", hash = "sha256:4948fdc77ff45fc5ef3d8330d17bbecfcb26cd9c4d3a4f00da84a41a0226cd72", size = 1046771, upload-time = "2026-06-16T19:46:27.949Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/89/4bda72ff33172ad15ddb2c57c5b998bf9309e7c4890d3d84a8c8ed6333d4/langgraph_cli-0.4.27-py3-none-any.whl", hash = "sha256:d53bddfc4e7b6e47871bedb34ccc476fd5e4e97f8678dd453ecac597c5f11ff1", size = 78002, upload-time = "2026-05-28T14:25:34.127Z" },
{ url = "https://files.pythonhosted.org/packages/3e/b6/94cbd2ba0820caae203a915272394c576a21ab4a56dfbc93724dc8cd8e2b/langgraph_cli-0.4.30-py3-none-any.whl", hash = "sha256:9c577750c57da1a0e3407e8b83e5a0d7eaa80685fe99d95aa7f9bf0e1e73ca92", size = 82061, upload-time = "2026-06-16T19:46:26.873Z" },
]
[package.optional-dependencies]
@ -2040,12 +2040,12 @@ requires-dist = [
{ name = "langchain-openai", specifier = ">=1.2.2" },
{ name = "langchain-runloop", specifier = ">=0.0.4" },
{ name = "langgraph", specifier = ">=1.1.10" },
{ name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.27" },
{ name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.30" },
{ name = "langgraph-sdk", specifier = ">=0.4.2" },
{ name = "langsmith", specifier = "==0.8.18" },
{ name = "markdownify", specifier = ">=1.2.2" },
{ name = "pygments", marker = "extra == 'dev'", specifier = ">=2.20.0" },
{ name = "pyjwt", specifier = ">=2.12.1" },
{ name = "pyjwt", specifier = ">=2.13.0" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.3" },
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.4.0" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.15" },
@ -2618,11 +2618,11 @@ wheels = [
[[package]]
name = "pyjwt"
version = "2.12.1"
version = "2.13.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" },
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
]
[package.optional-dependencies]