mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
feat: Make error slack messages only visable to user (#1018)
This commit is contained in:
parent
eb47c86c80
commit
659dd71433
3 changed files with 121 additions and 5 deletions
|
|
@ -14,7 +14,7 @@ from langgraph_sdk import get_client
|
||||||
|
|
||||||
from ..encryption import encrypt_token
|
from ..encryption import encrypt_token
|
||||||
from .linear import comment_on_linear_issue
|
from .linear import comment_on_linear_issue
|
||||||
from .slack import post_slack_thread_reply
|
from .slack import post_slack_ephemeral_message, post_slack_thread_reply
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -206,7 +206,33 @@ async def leave_failure_comment(
|
||||||
slack_thread = configurable.get("slack_thread", {})
|
slack_thread = configurable.get("slack_thread", {})
|
||||||
channel_id = slack_thread.get("channel_id") if isinstance(slack_thread, dict) else None
|
channel_id = slack_thread.get("channel_id") if isinstance(slack_thread, dict) else None
|
||||||
thread_ts = slack_thread.get("thread_ts") if isinstance(slack_thread, dict) else None
|
thread_ts = slack_thread.get("thread_ts") if isinstance(slack_thread, dict) else None
|
||||||
|
triggering_user_id = (
|
||||||
|
slack_thread.get("triggering_user_id") if isinstance(slack_thread, dict) else None
|
||||||
|
)
|
||||||
if channel_id and thread_ts:
|
if channel_id and thread_ts:
|
||||||
|
if isinstance(triggering_user_id, str) and triggering_user_id:
|
||||||
|
logger.info(
|
||||||
|
"Posting auth failure ephemeral reply to Slack user %s in channel %s thread %s",
|
||||||
|
triggering_user_id,
|
||||||
|
channel_id,
|
||||||
|
thread_ts,
|
||||||
|
)
|
||||||
|
sent = await post_slack_ephemeral_message(
|
||||||
|
channel_id=channel_id,
|
||||||
|
user_id=triggering_user_id,
|
||||||
|
text=message,
|
||||||
|
thread_ts=thread_ts,
|
||||||
|
)
|
||||||
|
if sent:
|
||||||
|
return
|
||||||
|
logger.warning(
|
||||||
|
"Failed to post ephemeral auth failure reply for Slack user %s; falling back to thread reply",
|
||||||
|
triggering_user_id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"Missing Slack triggering_user_id for auth failure reply; falling back to thread reply",
|
||||||
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"Posting auth failure reply to Slack channel %s thread %s",
|
"Posting auth failure reply to Slack channel %s thread %s",
|
||||||
channel_id,
|
channel_id,
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,39 @@ async def post_slack_thread_reply(channel_id: str, thread_ts: str, text: str) ->
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def post_slack_ephemeral_message(
|
||||||
|
channel_id: str, user_id: str, text: str, thread_ts: str | None = None
|
||||||
|
) -> bool:
|
||||||
|
"""Post an ephemeral message visible only to one user."""
|
||||||
|
if not SLACK_BOT_TOKEN:
|
||||||
|
return False
|
||||||
|
|
||||||
|
payload: dict[str, str] = {
|
||||||
|
"channel": channel_id,
|
||||||
|
"user": user_id,
|
||||||
|
"text": text,
|
||||||
|
}
|
||||||
|
if thread_ts:
|
||||||
|
payload["thread_ts"] = thread_ts
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as http_client:
|
||||||
|
try:
|
||||||
|
response = await http_client.post(
|
||||||
|
f"{SLACK_API_BASE_URL}/chat.postEphemeral",
|
||||||
|
headers=_slack_headers(),
|
||||||
|
json=payload,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("ok"):
|
||||||
|
logger.warning("Slack chat.postEphemeral failed: %s", data.get("error"))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except httpx.HTTPError:
|
||||||
|
logger.exception("Slack chat.postEphemeral request failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def add_slack_reaction(channel_id: str, message_ts: str, emoji: str = "eyes") -> bool:
|
async def add_slack_reaction(channel_id: str, message_ts: str, emoji: str = "eyes") -> bool:
|
||||||
"""Add a reaction to a Slack message."""
|
"""Add a reaction to a Slack message."""
|
||||||
if not SLACK_BOT_TOKEN:
|
if not SLACK_BOT_TOKEN:
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,76 @@ def test_leave_failure_comment_posts_to_slack_thread(
|
||||||
) -> None:
|
) -> None:
|
||||||
called: dict[str, str] = {}
|
called: dict[str, str] = {}
|
||||||
|
|
||||||
async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, message: str) -> bool:
|
async def fake_post_slack_ephemeral_message(
|
||||||
|
channel_id: str, user_id: str, text: str, thread_ts: str | None = None
|
||||||
|
) -> bool:
|
||||||
called["channel_id"] = channel_id
|
called["channel_id"] = channel_id
|
||||||
|
called["user_id"] = user_id
|
||||||
called["thread_ts"] = thread_ts
|
called["thread_ts"] = thread_ts
|
||||||
called["message"] = message
|
called["message"] = text
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, message: str) -> bool:
|
||||||
|
raise AssertionError("post_slack_thread_reply should not be called when ephemeral succeeds")
|
||||||
|
|
||||||
|
monkeypatch.setattr(auth, "post_slack_ephemeral_message", fake_post_slack_ephemeral_message)
|
||||||
monkeypatch.setattr(auth, "post_slack_thread_reply", fake_post_slack_thread_reply)
|
monkeypatch.setattr(auth, "post_slack_thread_reply", fake_post_slack_thread_reply)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
auth,
|
auth,
|
||||||
"get_config",
|
"get_config",
|
||||||
lambda: {"configurable": {"slack_thread": {"channel_id": "C123", "thread_ts": "1.2"}}},
|
lambda: {
|
||||||
|
"configurable": {
|
||||||
|
"slack_thread": {
|
||||||
|
"channel_id": "C123",
|
||||||
|
"thread_ts": "1.2",
|
||||||
|
"triggering_user_id": "U123",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
asyncio.run(auth.leave_failure_comment("slack", "auth failed"))
|
asyncio.run(auth.leave_failure_comment("slack", "auth failed"))
|
||||||
|
|
||||||
assert called == {"channel_id": "C123", "thread_ts": "1.2", "message": "auth failed"}
|
assert called == {
|
||||||
|
"channel_id": "C123",
|
||||||
|
"user_id": "U123",
|
||||||
|
"thread_ts": "1.2",
|
||||||
|
"message": "auth failed",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_leave_failure_comment_falls_back_to_slack_thread_when_ephemeral_fails(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
thread_called: dict[str, str] = {}
|
||||||
|
|
||||||
|
async def fake_post_slack_ephemeral_message(
|
||||||
|
channel_id: str, user_id: str, text: str, thread_ts: str | None = None
|
||||||
|
) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fake_post_slack_thread_reply(channel_id: str, thread_ts: str, message: str) -> bool:
|
||||||
|
thread_called["channel_id"] = channel_id
|
||||||
|
thread_called["thread_ts"] = thread_ts
|
||||||
|
thread_called["message"] = message
|
||||||
|
return True
|
||||||
|
|
||||||
|
monkeypatch.setattr(auth, "post_slack_ephemeral_message", fake_post_slack_ephemeral_message)
|
||||||
|
monkeypatch.setattr(auth, "post_slack_thread_reply", fake_post_slack_thread_reply)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
auth,
|
||||||
|
"get_config",
|
||||||
|
lambda: {
|
||||||
|
"configurable": {
|
||||||
|
"slack_thread": {
|
||||||
|
"channel_id": "C123",
|
||||||
|
"thread_ts": "1.2",
|
||||||
|
"triggering_user_id": "U123",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
asyncio.run(auth.leave_failure_comment("slack", "auth failed"))
|
||||||
|
|
||||||
|
assert thread_called == {"channel_id": "C123", "thread_ts": "1.2", "message": "auth failed"}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue