mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 06:53:14 +00:00
feat(open-swe): port upstream clean batch (#1775, #1785, #1789) (#215)
Some checks are pending
CI / Lint (push) Waiting to run
CI / Format check (push) Waiting to run
CI / Typecheck (push) Waiting to run
CI / Unit tests (push) Waiting to run
CI / Playwright E2E (push) Waiting to run
CI / Docker build smoke (push) Waiting to run
CI / Triage ledger up to date (push) Waiting to run
CI / ui bun.lock in sync (push) Waiting to run
Some checks are pending
CI / Lint (push) Waiting to run
CI / Format check (push) Waiting to run
CI / Typecheck (push) Waiting to run
CI / Unit tests (push) Waiting to run
CI / Playwright E2E (push) Waiting to run
CI / Docker build smoke (push) Waiting to run
CI / Triage ledger up to date (push) Waiting to run
CI / ui bun.lock in sync (push) Waiting to run
* Fix: Fix Improper privilege management in server.py (#1789) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 3ea29d3f231dd66bd7627769b5659564be4525df) * Fix: Fix Stored XSS in ReplyCard.tsx (#1785) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 31263f832a2ecedf669eee2e27b827a358a6a4d7) * feat: add structured Linear issue filters (#1775) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit b5e529252c3012818289eca1b1cdeb8014721310) * chore(triage): mark #1775 #1785 #1789 landed Move the three clean cherry-picks in this batch from deferred to landed in the upstream-sync ledger and re-render triage.md. --------- Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
parent
0f0f616cd4
commit
db05d5826f
8 changed files with 168 additions and 68 deletions
|
|
@ -6,6 +6,7 @@ import hmac
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request
|
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request
|
||||||
|
|
@ -377,12 +378,14 @@ async def auth_login(
|
||||||
nonce_hash=hash_state_nonce(nonce),
|
nonce_hash=hash_state_nonce(nonce),
|
||||||
)
|
)
|
||||||
redirect_uri = f"{_api_base_url()}/dashboard/api/auth/callback"
|
redirect_uri = f"{_api_base_url()}/dashboard/api/auth/callback"
|
||||||
url = (
|
query = urlencode(
|
||||||
"https://github.com/login/oauth/authorize"
|
{
|
||||||
f"?client_id={client_id}"
|
"client_id": client_id,
|
||||||
f"&redirect_uri={redirect_uri}"
|
"redirect_uri": redirect_uri,
|
||||||
f"&state={state}"
|
"state": state,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
url = f"https://github.com/login/oauth/authorize?{query}"
|
||||||
response = RedirectResponse(url, status_code=302)
|
response = RedirectResponse(url, status_code=302)
|
||||||
_set_state_cookie(response, nonce)
|
_set_state_cookie(response, nonce)
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,23 @@ from ..utils.linear import search_issues
|
||||||
|
|
||||||
|
|
||||||
async def linear_search_issues(
|
async def linear_search_issues(
|
||||||
query: str,
|
query: str | None = None,
|
||||||
team_id: str | None = None,
|
team_id: str | None = None,
|
||||||
|
filters: dict[str, Any] | None = None,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
include_archived: bool = False,
|
include_archived: bool = False,
|
||||||
include_comments: bool = False,
|
include_comments: bool = False,
|
||||||
after: str | None = None,
|
after: str | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Search Linear issues by title, description, and optionally comments.
|
"""Search Linear issues by text, structured filters, or both.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: Free-text search query.
|
query: Optional free-text query over issue content.
|
||||||
team_id: Optional team UUID used to restrict matches to that team.
|
team_id: Optional team UUID used to restrict matches to that team.
|
||||||
|
filters: Optional Linear IssueFilter object for labels, state, project, assignee, and more.
|
||||||
limit: Maximum results to return, from 1 to 50.
|
limit: Maximum results to return, from 1 to 50.
|
||||||
include_archived: Whether to include archived issues.
|
include_archived: Whether to include archived issues.
|
||||||
include_comments: Whether to search issue comments in addition to issue content.
|
include_comments: Whether free-text search includes issue comments.
|
||||||
after: Optional pagination cursor from a previous result's page_info.endCursor.
|
after: Optional pagination cursor from a previous result's page_info.endCursor.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -27,6 +29,7 @@ async def linear_search_issues(
|
||||||
return await search_issues(
|
return await search_issues(
|
||||||
query=query,
|
query=query,
|
||||||
team_id=team_id,
|
team_id=team_id,
|
||||||
|
filters=filters,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
include_archived=include_archived,
|
include_archived=include_archived,
|
||||||
include_comments=include_comments,
|
include_comments=include_comments,
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ def verify_github_signature(body: bytes, signature: str, *, secret: str) -> bool
|
||||||
logger.warning("GITHUB_WEBHOOK_SECRET is not configured — rejecting webhook request")
|
logger.warning("GITHUB_WEBHOOK_SECRET is not configured — rejecting webhook request")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not signature:
|
||||||
|
return False
|
||||||
|
|
||||||
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
|
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
|
||||||
return hmac.compare_digest(expected, signature)
|
return hmac.compare_digest(expected, signature)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,76 +133,110 @@ async def get_issue(issue_id: str) -> dict[str, Any]:
|
||||||
|
|
||||||
|
|
||||||
async def search_issues(
|
async def search_issues(
|
||||||
query: str,
|
query: str | None = None,
|
||||||
team_id: str | None = None,
|
team_id: str | None = None,
|
||||||
|
filters: dict[str, Any] | None = None,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
include_archived: bool = False,
|
include_archived: bool = False,
|
||||||
include_comments: bool = False,
|
include_comments: bool = False,
|
||||||
after: str | None = None,
|
after: str | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Search Linear issues by free-text query."""
|
"""Search Linear issues by text, structured filters, or both."""
|
||||||
query = query.strip()
|
query = (query or "").strip()
|
||||||
if not query:
|
issue_filter = dict(filters or {})
|
||||||
return {"error": "Search query must not be empty"}
|
if team_id:
|
||||||
|
team_filter = {"team": {"id": {"eq": team_id}}}
|
||||||
|
issue_filter = {"and": [issue_filter, team_filter]} if issue_filter else team_filter
|
||||||
|
if not query and not issue_filter:
|
||||||
|
return {"error": "Search query or filters must be provided"}
|
||||||
if not 1 <= limit <= 50:
|
if not 1 <= limit <= 50:
|
||||||
return {"error": "Search limit must be between 1 and 50"}
|
return {"error": "Search limit must be between 1 and 50"}
|
||||||
|
|
||||||
search_query = """
|
connection_fields = """
|
||||||
query SearchIssues(
|
totalCount
|
||||||
$query: String!
|
pageInfo {
|
||||||
$filter: IssueFilter
|
hasNextPage
|
||||||
$limit: Int!
|
endCursor
|
||||||
$includeArchived: Boolean
|
}
|
||||||
$includeComments: Boolean
|
nodes {
|
||||||
$after: String
|
id
|
||||||
) {
|
identifier
|
||||||
searchIssues(
|
title
|
||||||
term: $query
|
priority
|
||||||
filter: $filter
|
priorityLabel
|
||||||
first: $limit
|
state { id name type }
|
||||||
includeArchived: $includeArchived
|
assignee { id name email }
|
||||||
includeComments: $includeComments
|
team { id name key }
|
||||||
after: $after
|
project { id name }
|
||||||
) {
|
labels { nodes { id name } }
|
||||||
totalCount
|
createdAt
|
||||||
pageInfo {
|
updatedAt
|
||||||
hasNextPage
|
archivedAt
|
||||||
endCursor
|
url
|
||||||
}
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
identifier
|
|
||||||
title
|
|
||||||
priority
|
|
||||||
priorityLabel
|
|
||||||
state { id name type }
|
|
||||||
assignee { id name email }
|
|
||||||
team { id name key }
|
|
||||||
project { id name }
|
|
||||||
labels { nodes { id name } }
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
archivedAt
|
|
||||||
url
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
"""
|
"""
|
||||||
result = await _graphql_request(
|
if query:
|
||||||
search_query,
|
graphql_query = f"""
|
||||||
{
|
query SearchIssues(
|
||||||
|
$query: String!
|
||||||
|
$filter: IssueFilter
|
||||||
|
$limit: Int!
|
||||||
|
$includeArchived: Boolean
|
||||||
|
$includeComments: Boolean
|
||||||
|
$after: String
|
||||||
|
) {{
|
||||||
|
searchIssues(
|
||||||
|
term: $query
|
||||||
|
filter: $filter
|
||||||
|
first: $limit
|
||||||
|
includeArchived: $includeArchived
|
||||||
|
includeComments: $includeComments
|
||||||
|
after: $after
|
||||||
|
) {{
|
||||||
|
{connection_fields}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
variables = {
|
||||||
"query": query,
|
"query": query,
|
||||||
"filter": {"team": {"id": {"eq": team_id}}} if team_id else None,
|
"filter": issue_filter or None,
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
"includeArchived": include_archived,
|
"includeArchived": include_archived,
|
||||||
"includeComments": include_comments,
|
"includeComments": include_comments,
|
||||||
"after": after,
|
"after": after,
|
||||||
},
|
}
|
||||||
)
|
connection_name = "searchIssues"
|
||||||
|
else:
|
||||||
|
graphql_query = f"""
|
||||||
|
query FilterIssues(
|
||||||
|
$filter: IssueFilter!
|
||||||
|
$limit: Int!
|
||||||
|
$includeArchived: Boolean
|
||||||
|
$after: String
|
||||||
|
) {{
|
||||||
|
issues(
|
||||||
|
filter: $filter
|
||||||
|
first: $limit
|
||||||
|
includeArchived: $includeArchived
|
||||||
|
after: $after
|
||||||
|
) {{
|
||||||
|
{connection_fields}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
variables = {
|
||||||
|
"filter": issue_filter,
|
||||||
|
"limit": limit,
|
||||||
|
"includeArchived": include_archived,
|
||||||
|
"after": after,
|
||||||
|
}
|
||||||
|
connection_name = "issues"
|
||||||
|
|
||||||
|
result = await _graphql_request(graphql_query, variables)
|
||||||
if "error" in result:
|
if "error" in result:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
search_results = result.get("searchIssues", {})
|
search_results = result.get(connection_name, {})
|
||||||
return {
|
return {
|
||||||
"issues": search_results.get("nodes", []),
|
"issues": search_results.get("nodes", []),
|
||||||
"total_count": search_results.get("totalCount", 0),
|
"total_count": search_results.get("totalCount", 0),
|
||||||
|
|
|
||||||
|
|
@ -1164,6 +1164,9 @@ def verify_linear_signature(body: bytes, signature: str, secret: str) -> bool:
|
||||||
logger.warning("LINEAR_WEBHOOK_SECRET is not configured — rejecting webhook request")
|
logger.warning("LINEAR_WEBHOOK_SECRET is not configured — rejecting webhook request")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not signature:
|
||||||
|
return False
|
||||||
|
|
||||||
expected = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
|
expected = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
|
||||||
if not hmac.compare_digest(expected, signature):
|
if not hmac.compare_digest(expected, signature):
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -124,9 +124,9 @@
|
||||||
{"sha": "dccf6437", "pr": 1769, "subject": "fix: tighten sandbox config test types (#1769)", "disposition": "wont-merge", "reason": "test-only change in post-reorg path tests/sandbox/ — fork doesn't have this file (domain reorg #1726 deferred)", "branch": "", "local_sha": null, "updated": "2026-07-17T12:42:37Z"}
|
{"sha": "dccf6437", "pr": 1769, "subject": "fix: tighten sandbox config test types (#1769)", "disposition": "wont-merge", "reason": "test-only change in post-reorg path tests/sandbox/ — fork doesn't have this file (domain reorg #1726 deferred)", "branch": "", "local_sha": null, "updated": "2026-07-17T12:42:37Z"}
|
||||||
{"sha": "c9a193e2", "pr": 1766, "subject": "chore(deps): bump mcp from 1.27.2 to 1.28.1 (#1766)", "disposition": "wont-merge", "reason": "indirect dependency bump (mcp); fork's own Dependabot handles these", "branch": "", "local_sha": null, "updated": "2026-07-17T12:42:37Z"}
|
{"sha": "c9a193e2", "pr": 1766, "subject": "chore(deps): bump mcp from 1.27.2 to 1.28.1 (#1766)", "disposition": "wont-merge", "reason": "indirect dependency bump (mcp); fork's own Dependabot handles these", "branch": "", "local_sha": null, "updated": "2026-07-17T12:42:37Z"}
|
||||||
{"sha": "d0b63551", "pr": 1773, "subject": "fix: remove workflow push approval gating (#1773)", "disposition": "wont-merge", "reason": "Removes WorkflowPushGuardMiddleware and the proxy-token permission ladder — both actively wired in this fork (server.py middleware stack; github_app.py scoped-mint fallback). Adopting would let agent runs push .github/workflows/ changes with no human approval and mint proxy tokens at full scope unconditionally — a security-posture loosening counter to Sea Haven gating. Keep the fork's guard; skip the doc/prompt relaxation too (fork prompt documents the approval flow).", "branch": "", "local_sha": null, "updated": "2026-07-17T22:33:45Z"}
|
{"sha": "d0b63551", "pr": 1773, "subject": "fix: remove workflow push approval gating (#1773)", "disposition": "wont-merge", "reason": "Removes WorkflowPushGuardMiddleware and the proxy-token permission ladder — both actively wired in this fork (server.py middleware stack; github_app.py scoped-mint fallback). Adopting would let agent runs push .github/workflows/ changes with no human approval and mint proxy tokens at full scope unconditionally — a security-posture loosening counter to Sea Haven gating. Keep the fork's guard; skip the doc/prompt relaxation too (fork prompt documents the approval flow).", "branch": "", "local_sha": null, "updated": "2026-07-17T22:33:45Z"}
|
||||||
{"sha": "b5e52925", "pr": 1775, "subject": "feat: add structured Linear issue filters (#1775)", "disposition": "deferred", "reason": "Clean pick: structured filters for linear_search_issues — stacks directly on #1748 (landed PR #206); zero fork drift on all three files. Ready whenever.", "branch": "linear-tooling", "local_sha": null, "updated": "2026-07-17T22:33:45Z"}
|
{"sha": "b5e52925", "pr": 1775, "subject": "feat: add structured Linear issue filters (#1775)", "disposition": "landed", "reason": "Clean pick: structured filters for linear_search_issues — stacks directly on #1748 (landed PR #206); zero fork drift on all three files. Ready whenever.", "branch": "feature/upstream-clean-batch-security-linear", "local_sha": null, "updated": "2026-07-20T19:46:00Z"}
|
||||||
{"sha": "81d544bc", "pr": 1765, "subject": "feat: Expose more specific AGENTS.md context to Open SWE reviewer (#1765)", "disposition": "deferred", "reason": "Near-clean: agents_md.py helper + tests are zero-drift; reviewer.py hunk is small (~39 lines) but lands in the fork's heavily-diverged reviewer — hand-apply the scoped_agents_md wiring onto the fork's fetch_agents_md call sites (reviewer.py ~L404/445/1031).", "branch": "reviewer-context", "local_sha": null, "updated": "2026-07-17T22:33:45Z"}
|
{"sha": "81d544bc", "pr": 1765, "subject": "feat: Expose more specific AGENTS.md context to Open SWE reviewer (#1765)", "disposition": "deferred", "reason": "Near-clean: agents_md.py helper + tests are zero-drift; reviewer.py hunk is small (~39 lines) but lands in the fork's heavily-diverged reviewer — hand-apply the scoped_agents_md wiring onto the fork's fetch_agents_md call sites (reviewer.py ~L404/445/1031).", "branch": "reviewer-context", "local_sha": null, "updated": "2026-07-17T22:33:45Z"}
|
||||||
{"sha": "8c8e58bc", "pr": 1776, "subject": "feat: connect automations to Slack channels (#1776)", "disposition": "deferred", "reason": "Moderate reconcile: Slack-channel wiring for automations. Fork helpers exist (post_slack_top_level_message_with_ts, generate_thread_id_from_slack_thread, slack_id_for_login); completion.py (+233 drift) and schedules.py (+167) need hand-merge; UI automations feature present. Keep backend+UI+tests as one vertical.", "branch": "automations-slack", "local_sha": null, "updated": "2026-07-17T22:33:46Z"}
|
{"sha": "8c8e58bc", "pr": 1776, "subject": "feat: connect automations to Slack channels (#1776)", "disposition": "deferred", "reason": "Moderate reconcile: Slack-channel wiring for automations. Fork helpers exist (post_slack_top_level_message_with_ts, generate_thread_id_from_slack_thread, slack_id_for_login); completion.py (+233 drift) and schedules.py (+167) need hand-merge; UI automations feature present. Keep backend+UI+tests as one vertical.", "branch": "automations-slack", "local_sha": null, "updated": "2026-07-17T22:33:46Z"}
|
||||||
{"sha": "f0897479", "pr": 1778, "subject": "feat: surface context window usage in agents UI (#1778)", "disposition": "deferred", "reason": "Near-clean: context-window indicator UI is all new files (zero drift); options.py hunk must be re-keyed to the fork's Bedrock/Fireworks model map (fork model IDs differ from upstream's) — add context_window per fork entry rather than taking upstream values.", "branch": "context-usage-ui", "local_sha": null, "updated": "2026-07-17T22:33:46Z"}
|
{"sha": "f0897479", "pr": 1778, "subject": "feat: surface context window usage in agents UI (#1778)", "disposition": "deferred", "reason": "Near-clean: context-window indicator UI is all new files (zero drift); options.py hunk must be re-keyed to the fork's Bedrock/Fireworks model map (fork model IDs differ from upstream's) — add context_window per fork entry rather than taking upstream values.", "branch": "context-usage-ui", "local_sha": null, "updated": "2026-07-17T22:33:46Z"}
|
||||||
{"sha": "31263f83", "pr": 1785, "subject": "Fix: Fix Stored XSS in ReplyCard.tsx (#1785)", "disposition": "deferred", "reason": "SECURITY priority, near-clean: diff is urlencode() hardening of the GitHub OAuth authorize URL in dashboard routes.py auth_login (upstream bot title says ReplyCard.tsx — mismatch, trust the diff). Fork auth_login has the identical f-string URL; hunk applies clean. Port promptly.", "branch": "sec-oauth-urlencode", "local_sha": null, "updated": "2026-07-20T18:06:01Z"}
|
{"sha": "31263f83", "pr": 1785, "subject": "Fix: Fix Stored XSS in ReplyCard.tsx (#1785)", "disposition": "landed", "reason": "SECURITY priority, near-clean: diff is urlencode() hardening of the GitHub OAuth authorize URL in dashboard routes.py auth_login (upstream bot title says ReplyCard.tsx — mismatch, trust the diff). Fork auth_login has the identical f-string URL; hunk applies clean. Port promptly.", "branch": "feature/upstream-clean-batch-security-linear", "local_sha": null, "updated": "2026-07-20T19:46:00Z"}
|
||||||
{"sha": "3ea29d3f", "pr": 1789, "subject": "Fix: Fix Improper privilege management in server.py (#1789)", "disposition": "deferred", "reason": "SECURITY priority, near-clean: adds empty-signature reject to verify_github_signature (utils/github_comments.py) + verify_linear_signature (webhooks/common.py) (title says server.py — mismatch, trust the diff). Both fork functions match at the hunk sites; fork-only verify_jira_secret already guards empty token. Real value: None signature currently raises TypeError in compare_digest. Port BOTH hunks together.", "branch": "sec-webhook-empty-sig", "local_sha": null, "updated": "2026-07-20T18:06:01Z"}
|
{"sha": "3ea29d3f", "pr": 1789, "subject": "Fix: Fix Improper privilege management in server.py (#1789)", "disposition": "landed", "reason": "SECURITY priority, near-clean: adds empty-signature reject to verify_github_signature (utils/github_comments.py) + verify_linear_signature (webhooks/common.py) (title says server.py — mismatch, trust the diff). Both fork functions match at the hunk sites; fork-only verify_jira_secret already guards empty token. Real value: None signature currently raises TypeError in compare_digest. Port BOTH hunks together.", "branch": "feature/upstream-clean-batch-security-linear", "local_sha": null, "updated": "2026-07-20T19:46:00Z"}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,9 @@ Rows key on the **upstream SHA** (stable across local cherry-picks). Deferred ro
|
||||||
| `22383033` | #1758 | feat: inject extra JSON fields into sandbox create via env var (#1758) | Landed | Landed via fork PR #206, re-implemented against the fork's sync SandboxClient (upstream is async AsyncSandboxClient — retry/reconnect helpers not adopted; future picks touching them will conflict). | feature/port-upstream-clean-batch |
|
| `22383033` | #1758 | feat: inject extra JSON fields into sandbox create via env var (#1758) | Landed | Landed via fork PR #206, re-implemented against the fork's sync SandboxClient (upstream is async AsyncSandboxClient — retry/reconnect helpers not adopted; future picks touching them will conflict). | feature/port-upstream-clean-batch |
|
||||||
| `e826864d` | #1760 | feat: optional separate LangSmith key/endpoint for sandboxes (#1760) | Landed | Landed via fork PR #206 on the sync client. Sandbox endpoint honors SANDBOX_LANGSMITH_ENDPOINT/LANGSMITH_ENDPOINT (LANGCHAIN_ENDPOINT alone no longer applies); new sandbox names thread-deterministic with _release_sandbox_name before create. | feature/port-upstream-clean-batch |
|
| `e826864d` | #1760 | feat: optional separate LangSmith key/endpoint for sandboxes (#1760) | Landed | Landed via fork PR #206 on the sync client. Sandbox endpoint honors SANDBOX_LANGSMITH_ENDPOINT/LANGSMITH_ENDPOINT (LANGCHAIN_ENDPOINT alone no longer applies); new sandbox names thread-deterministic with _release_sandbox_name before create. | feature/port-upstream-clean-batch |
|
||||||
| `dd5b7bec` | #1761 | fix: capitalize dashboard tool labels (#1761) | Landed | Landed via fork PR #206 (clean cherry-pick, stacked on #1732). | feature/port-upstream-clean-batch |
|
| `dd5b7bec` | #1761 | fix: capitalize dashboard tool labels (#1761) | Landed | Landed via fork PR #206 (clean cherry-pick, stacked on #1732). | feature/port-upstream-clean-batch |
|
||||||
|
| `b5e52925` | #1775 | feat: add structured Linear issue filters (#1775) | Landed | Clean pick: structured filters for linear_search_issues — stacks directly on #1748 (landed PR #206); zero fork drift on all three files. Ready whenever. | feature/upstream-clean-batch-security-linear |
|
||||||
|
| `31263f83` | #1785 | Fix: Fix Stored XSS in ReplyCard.tsx (#1785) | Landed | SECURITY priority, near-clean: diff is urlencode() hardening of the GitHub OAuth authorize URL in dashboard routes.py auth_login (upstream bot title says ReplyCard.tsx — mismatch, trust the diff). Fork auth_login has the identical f-string URL; hunk applies clean. Port promptly. | feature/upstream-clean-batch-security-linear |
|
||||||
|
| `3ea29d3f` | #1789 | Fix: Fix Improper privilege management in server.py (#1789) | Landed | SECURITY priority, near-clean: adds empty-signature reject to verify_github_signature (utils/github_comments.py) + verify_linear_signature (webhooks/common.py) (title says server.py — mismatch, trust the diff). Both fork functions match at the hunk sites; fork-only verify_jira_secret already guards empty token. Real value: None signature currently raises TypeError in compare_digest. Port BOTH hunks together. | feature/upstream-clean-batch-security-linear |
|
||||||
| `c3292d82` | #1611 | bake sfw binary into sandbox image | Won't merge | already in dev | |
|
| `c3292d82` | #1611 | bake sfw binary into sandbox image | Won't merge | already in dev | |
|
||||||
| `48bf712b` | #1609 | show message timestamps | Won't merge | already in dev | |
|
| `48bf712b` | #1609 | show message timestamps | Won't merge | already in dev | |
|
||||||
| `85c0f63e` | #1620 | clickable shared PR header | Won't merge | already in dev | |
|
| `85c0f63e` | #1620 | clickable shared PR header | Won't merge | already in dev | |
|
||||||
|
|
@ -135,11 +138,8 @@ Rows key on the **upstream SHA** (stable across local cherry-picks). Deferred ro
|
||||||
| `5077e2c7` | #1735 | feat(open-swe): untagged two-party Slack replies + debounced interrupts (#1735) | Deferred | untagged two-party Slack replies + debounced interrupts (~620 LOC incl. e2e); rides fork-diverged Slack webhook stack; own branch + e2e validation | slack-untagged-replies |
|
| `5077e2c7` | #1735 | feat(open-swe): untagged two-party Slack replies + debounced interrupts (#1735) | Deferred | untagged two-party Slack replies + debounced interrupts (~620 LOC incl. e2e); rides fork-diverged Slack webhook stack; own branch + e2e validation | slack-untagged-replies |
|
||||||
| `8b26819f` | #1719 | fix: offload web tool results to sandbox (#1719) | Deferred | offloads large web tool results to sandbox files; touches fork-relevant web_search/http tools; check interplay with fork sandbox lifecycle | tool-offloading |
|
| `8b26819f` | #1719 | fix: offload web tool results to sandbox (#1719) | Deferred | offloads large web tool results to sandbox files; touches fork-relevant web_search/http tools; check interplay with fork sandbox lifecycle | tool-offloading |
|
||||||
| `b7c5dbd6` | #1747 | fix: simplify Slack run links (#1747) | Deferred | simplifies Slack run links; heavy churn on fork-diverged Slack context/prompt tests | slack-tooling |
|
| `b7c5dbd6` | #1747 | fix: simplify Slack run links (#1747) | Deferred | simplifies Slack run links; heavy churn on fork-diverged Slack context/prompt tests | slack-tooling |
|
||||||
| `b5e52925` | #1775 | feat: add structured Linear issue filters (#1775) | Deferred | Clean pick: structured filters for linear_search_issues — stacks directly on #1748 (landed PR #206); zero fork drift on all three files. Ready whenever. | linear-tooling |
|
|
||||||
| `81d544bc` | #1765 | feat: Expose more specific AGENTS.md context to Open SWE reviewer (#1765) | Deferred | Near-clean: agents_md.py helper + tests are zero-drift; reviewer.py hunk is small (~39 lines) but lands in the fork's heavily-diverged reviewer — hand-apply the scoped_agents_md wiring onto the fork's fetch_agents_md call sites (reviewer.py ~L404/445/1031). | reviewer-context |
|
| `81d544bc` | #1765 | feat: Expose more specific AGENTS.md context to Open SWE reviewer (#1765) | Deferred | Near-clean: agents_md.py helper + tests are zero-drift; reviewer.py hunk is small (~39 lines) but lands in the fork's heavily-diverged reviewer — hand-apply the scoped_agents_md wiring onto the fork's fetch_agents_md call sites (reviewer.py ~L404/445/1031). | reviewer-context |
|
||||||
| `8c8e58bc` | #1776 | feat: connect automations to Slack channels (#1776) | Deferred | Moderate reconcile: Slack-channel wiring for automations. Fork helpers exist (post_slack_top_level_message_with_ts, generate_thread_id_from_slack_thread, slack_id_for_login); completion.py (+233 drift) and schedules.py (+167) need hand-merge; UI automations feature present. Keep backend+UI+tests as one vertical. | automations-slack |
|
| `8c8e58bc` | #1776 | feat: connect automations to Slack channels (#1776) | Deferred | Moderate reconcile: Slack-channel wiring for automations. Fork helpers exist (post_slack_top_level_message_with_ts, generate_thread_id_from_slack_thread, slack_id_for_login); completion.py (+233 drift) and schedules.py (+167) need hand-merge; UI automations feature present. Keep backend+UI+tests as one vertical. | automations-slack |
|
||||||
| `f0897479` | #1778 | feat: surface context window usage in agents UI (#1778) | Deferred | Near-clean: context-window indicator UI is all new files (zero drift); options.py hunk must be re-keyed to the fork's Bedrock/Fireworks model map (fork model IDs differ from upstream's) — add context_window per fork entry rather than taking upstream values. | context-usage-ui |
|
| `f0897479` | #1778 | feat: surface context window usage in agents UI (#1778) | Deferred | Near-clean: context-window indicator UI is all new files (zero drift); options.py hunk must be re-keyed to the fork's Bedrock/Fireworks model map (fork model IDs differ from upstream's) — add context_window per fork entry rather than taking upstream values. | context-usage-ui |
|
||||||
| `31263f83` | #1785 | Fix: Fix Stored XSS in ReplyCard.tsx (#1785) | Deferred | SECURITY priority, near-clean: diff is urlencode() hardening of the GitHub OAuth authorize URL in dashboard routes.py auth_login (upstream bot title says ReplyCard.tsx — mismatch, trust the diff). Fork auth_login has the identical f-string URL; hunk applies clean. Port promptly. | sec-oauth-urlencode |
|
|
||||||
| `3ea29d3f` | #1789 | Fix: Fix Improper privilege management in server.py (#1789) | Deferred | SECURITY priority, near-clean: adds empty-signature reject to verify_github_signature (utils/github_comments.py) + verify_linear_signature (webhooks/common.py) (title says server.py — mismatch, trust the diff). Both fork functions match at the hunk sites; fork-only verify_jira_secret already guards empty token. Real value: None signature currently raises TypeError in compare_digest. Port BOTH hunks together. | sec-webhook-empty-sig |
|
|
||||||
|
|
||||||
_Maintenance: after a `git sync`, add new `dev..upstream/main` SHAs as **Untriaged** (edit `triage.jsonl`) and bump "Last synced". A successful `git cherry-pick -x` auto-moves the row to **Landed** via the `post-commit` journal + `make triage-reconcile`._
|
_Maintenance: after a `git sync`, add new `dev..upstream/main` SHAs as **Untriaged** (edit `triage.jsonl`) and bump "Last synced". A successful `git cherry-pick -x` auto-moves the row to **Landed** via the `post-commit` journal + `make triage-reconcile`._
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,59 @@ async def test_search_issues_returns_results_and_pagination(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_search_issues_rejects_blank_query(monkeypatch: pytest.MonkeyPatch) -> None:
|
async def test_search_issues_filters_without_text(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
captured: dict[str, Any] = {}
|
||||||
|
|
||||||
|
async def fake_graphql_request(
|
||||||
|
query: str, variables: dict[str, Any] | None = None
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
captured.update({"query": query, "variables": variables})
|
||||||
|
return {
|
||||||
|
"issues": {
|
||||||
|
"nodes": [{"id": "issue-id", "identifier": "DCD-21", "title": "Fix filters"}],
|
||||||
|
"totalCount": 1,
|
||||||
|
"pageInfo": {"hasNextPage": False, "endCursor": None},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
monkeypatch.setattr(linear, "_graphql_request", fake_graphql_request)
|
||||||
|
filters = {"labels": {"some": {"name": {"eq": "open-swe"}}}}
|
||||||
|
|
||||||
|
result = await linear.search_issues(filters=filters, limit=1)
|
||||||
|
|
||||||
|
assert "issues(" in captured["query"]
|
||||||
|
assert "searchIssues" not in captured["query"]
|
||||||
|
assert captured["variables"] == {
|
||||||
|
"filter": filters,
|
||||||
|
"limit": 1,
|
||||||
|
"includeArchived": False,
|
||||||
|
"after": None,
|
||||||
|
}
|
||||||
|
assert result["issues"][0]["identifier"] == "DCD-21"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_search_issues_combines_filters_with_team(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
captured: dict[str, Any] = {}
|
||||||
|
|
||||||
|
async def fake_graphql_request(
|
||||||
|
_query: str, variables: dict[str, Any] | None = None
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
captured["variables"] = variables
|
||||||
|
return {"searchIssues": {"nodes": [], "totalCount": 0, "pageInfo": {}}}
|
||||||
|
|
||||||
|
monkeypatch.setattr(linear, "_graphql_request", fake_graphql_request)
|
||||||
|
filters = {"state": {"name": {"eq": "Todo"}}}
|
||||||
|
|
||||||
|
await linear.search_issues("fix", team_id="team-id", filters=filters)
|
||||||
|
|
||||||
|
assert captured["variables"]["filter"] == {
|
||||||
|
"and": [filters, {"team": {"id": {"eq": "team-id"}}}]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_search_issues_rejects_missing_query_and_filters(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
async def unexpected_request(*_args: Any, **_kwargs: Any) -> dict[str, Any]:
|
async def unexpected_request(*_args: Any, **_kwargs: Any) -> dict[str, Any]:
|
||||||
pytest.fail("GraphQL request should not be made")
|
pytest.fail("GraphQL request should not be made")
|
||||||
|
|
||||||
|
|
@ -74,7 +126,7 @@ async def test_search_issues_rejects_blank_query(monkeypatch: pytest.MonkeyPatch
|
||||||
|
|
||||||
result = await linear.search_issues(" ")
|
result = await linear.search_issues(" ")
|
||||||
|
|
||||||
assert result == {"error": "Search query must not be empty"}
|
assert result == {"error": "Search query or filters must be provided"}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("limit", [0, 51])
|
@pytest.mark.parametrize("limit", [0, 51])
|
||||||
|
|
@ -114,6 +166,7 @@ async def test_linear_search_issues_tool_delegates(monkeypatch: pytest.MonkeyPat
|
||||||
result = await linear_search_tool.linear_search_issues(
|
result = await linear_search_tool.linear_search_issues(
|
||||||
"styling",
|
"styling",
|
||||||
team_id="team-id",
|
team_id="team-id",
|
||||||
|
filters={"priority": {"eq": 1}},
|
||||||
limit=20,
|
limit=20,
|
||||||
include_archived=True,
|
include_archived=True,
|
||||||
include_comments=True,
|
include_comments=True,
|
||||||
|
|
@ -124,6 +177,7 @@ async def test_linear_search_issues_tool_delegates(monkeypatch: pytest.MonkeyPat
|
||||||
assert captured == {
|
assert captured == {
|
||||||
"query": "styling",
|
"query": "styling",
|
||||||
"team_id": "team-id",
|
"team_id": "team-id",
|
||||||
|
"filters": {"priority": {"eq": 1}},
|
||||||
"limit": 20,
|
"limit": 20,
|
||||||
"include_archived": True,
|
"include_archived": True,
|
||||||
"include_comments": True,
|
"include_comments": True,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue