mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 20:53:15 +00:00
fix: resolve dashboard run email from GitHub mapping, not OAuth profile (#1372)
When sending a message to a thread from the Agents chat UI, the run used
the OAuth profile email (which can be a personal account that isn't an org
member). For Slack-originated threads resumed from the UI, auth routes
through the email-based path and fails ("Could not find a LangSmith account").
Resolve the run email via the GitHub->email mapping first, falling back to
the profile email only when no mapping exists, matching the github-source
auth path.
This commit is contained in:
parent
c41185a3ca
commit
7a9785c9d3
2 changed files with 70 additions and 2 deletions
|
|
@ -21,6 +21,7 @@ from .message_adapter import state_messages_to_ui
|
|||
from .options import SUPPORTED_MODEL_IDS, model_supports_effort
|
||||
from .profiles import OAUTH_TOKENS_NAMESPACE, get_profile, get_valid_access_token
|
||||
from .profiles import _get_value as get_oauth_record
|
||||
from .user_mappings import email_for_login
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -36,6 +37,17 @@ def _agent_version_metadata() -> dict[str, str]:
|
|||
return {"LANGSMITH_AGENT_VERSION": revision} if revision else {}
|
||||
|
||||
|
||||
async def _resolve_run_email(login: str, profile: dict[str, Any]) -> str | None:
|
||||
"""Email used for GitHub/LangSmith auth on a run.
|
||||
|
||||
Prefers the admin/self GitHub→email mapping (the work email known to
|
||||
the org) over the OAuth profile email, which may be a personal account
|
||||
that isn't an org member.
|
||||
"""
|
||||
mapped = await email_for_login(login)
|
||||
return mapped or profile.get("email")
|
||||
|
||||
|
||||
class ThreadCreateBody(BaseModel):
|
||||
prompt: str = Field(min_length=1, max_length=20_000)
|
||||
repo: str | None = None
|
||||
|
|
@ -320,7 +332,7 @@ async def _start_agent_run(
|
|||
"source": _DASHBOARD_SOURCE,
|
||||
"github_login": login,
|
||||
"repo": repo_config,
|
||||
"user_email": profile.get("email"),
|
||||
"user_email": await _resolve_run_email(login, profile),
|
||||
}
|
||||
if chosen_model and chosen_effort:
|
||||
configurable["agent_model_id"] = chosen_model
|
||||
|
|
@ -400,7 +412,7 @@ async def send_dashboard_message(
|
|||
"source": thread_source,
|
||||
"github_login": login,
|
||||
"repo": {"owner": owner, "name": name},
|
||||
"user_email": profile.get("email"),
|
||||
"user_email": await _resolve_run_email(login, profile),
|
||||
}
|
||||
source_context = metadata.get("source_context")
|
||||
if isinstance(source_context, dict):
|
||||
|
|
|
|||
56
tests/test_dashboard_run_email.py
Normal file
56
tests/test_dashboard_run_email.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from agent.dashboard import thread_api
|
||||
from agent.dashboard import user_mappings as um
|
||||
|
||||
|
||||
class _FakeStore:
|
||||
def __init__(self) -> None:
|
||||
self.items: dict[tuple[tuple[str, ...], str], dict[str, Any]] = {}
|
||||
|
||||
async def get_item(self, namespace: list[str], key: str):
|
||||
value = self.items.get((tuple(namespace), key))
|
||||
return {"value": value} if value is not None else None
|
||||
|
||||
async def put_item(self, namespace: list[str], key: str, value: dict[str, Any]) -> None:
|
||||
self.items[(tuple(namespace), key)] = value
|
||||
|
||||
async def search_items(self, namespace: list[str], *, limit: int = 1000):
|
||||
ns = tuple(namespace)
|
||||
items = [{"value": v} for (n, _k), v in self.items.items() if n == ns]
|
||||
return {"items": items[:limit]}
|
||||
|
||||
|
||||
class _FakeClient:
|
||||
def __init__(self, store: _FakeStore) -> None:
|
||||
self.store = store
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def fake_store(monkeypatch: pytest.MonkeyPatch) -> _FakeStore:
|
||||
store = _FakeStore()
|
||||
monkeypatch.setattr(um, "_client", lambda: _FakeClient(store))
|
||||
um.clear_cache()
|
||||
return store
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_email_prefers_github_mapping(fake_store: _FakeStore) -> None:
|
||||
await um.upsert_mapping(
|
||||
github_login="johannes117",
|
||||
work_email="johannes@langchain.dev",
|
||||
source="admin",
|
||||
)
|
||||
# OAuth profile carries a personal account that isn't an org member.
|
||||
profile = {"email": "johannesduplessis117@gmail.com"}
|
||||
assert await thread_api._resolve_run_email("johannes117", profile) == "johannes@langchain.dev"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_email_falls_back_to_profile_when_unmapped(fake_store: _FakeStore) -> None:
|
||||
profile = {"email": "someone@example.com"}
|
||||
assert await thread_api._resolve_run_email("nomapping", profile) == "someone@example.com"
|
||||
Loading…
Add table
Reference in a new issue