Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7c2e23c
[Add] Implement Slack Socket Mode support
JCMarques15 Oct 15, 2024
c58c1eb
[Fix] Correct comment for Slack socket handler env variable
JCMarques15 Oct 15, 2024
f1aa978
[Fix] Correct Slack token reference in app.py docstring
JCMarques15 Oct 15, 2024
470d5ac
Merge remote-tracking branch 'upstream/main' into enable-slack-websocket
JCMarques15 Oct 17, 2024
ef30091
Merge branch 'main' into enable-slack-websocket
dokterbob Nov 6, 2024
affc956
[Add] Update gitignore with node dependencies
JCMarques15 Apr 30, 2025
5da9a80
[Cut] Move Slack websocket test to Cookbook repo
JCMarques15 Apr 30, 2025
9e6a9aa
[Add] Slack Socket Mode tests
JCMarques15 Apr 30, 2025
ec8298d
Merge branch 'main' into enable-slack-websocket
JCMarques15 Apr 30, 2025
3436dfd
Merge branch 'main' into enable-slack-websocket
asvishnyakov Jul 18, 2025
5c1b321
Clean up Slack socket mode tests: remove unused import and fix commen…
JCMarques15 Jul 21, 2025
e26d353
Merge remote-tracking branch 'origin/main' into pr/JCMarques15/1436
JCMarques15 Jul 21, 2025
6c14508
Fix CI/CD test failure in slack socket mode tests
JCMarques15 Jul 21, 2025
5c0f433
Improve code formatting in Slack handler sections
JCMarques15 Jul 22, 2025
8857a30
Merge branch 'main' into enable-slack-websocket
hayescode Jul 23, 2025
c30b121
Merge branch 'main' into enable-slack-websocket
asvishnyakov Aug 7, 2025
8d4bc9b
Merge branch 'main' into enable-slack-websocket
hayescode Aug 14, 2025
410a822
Merge branch 'main' into enable-slack-websocket
hayescode Aug 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pnpm-debug.log*
lerna-debug.log*

node_modules
.pnpm-store
dist
dist-ssr
*.local
Expand All @@ -61,4 +62,4 @@ dist-ssr
backend/README.md
backend/.dmypy.json

.history
.history
20 changes: 18 additions & 2 deletions backend/chainlit/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ async def watch_files_for_changes():

discord_task = asyncio.create_task(client.start(discord_bot_token))

slack_task = None

# Slack Socket Handler if env variable SLACK_WEBSOCKET_TOKEN is set
if os.environ.get("SLACK_BOT_TOKEN") and os.environ.get("SLACK_WEBSOCKET_TOKEN"):
from chainlit.slack.app import start_socket_mode

slack_task = asyncio.create_task(start_socket_mode())

try:
yield
finally:
Expand All @@ -162,6 +170,10 @@ async def watch_files_for_changes():
if discord_task:
discord_task.cancel()
await discord_task

if slack_task:
slack_task.cancel()
await slack_task
except asyncio.exceptions.CancelledError:
pass

Expand Down Expand Up @@ -294,10 +306,14 @@ async def serve_copilot_file(


# -------------------------------------------------------------------------------
# SLACK HANDLER
# SLACK HTTP HANDLER
# -------------------------------------------------------------------------------

if os.environ.get("SLACK_BOT_TOKEN") and os.environ.get("SLACK_SIGNING_SECRET"):
if (
os.environ.get("SLACK_BOT_TOKEN")
and os.environ.get("SLACK_SIGNING_SECRET")
and not os.environ.get("SLACK_WEBSOCKET_TOKEN")
):
from chainlit.slack.app import slack_app_handler

@router.post("/slack/events")
Expand Down
11 changes: 11 additions & 0 deletions backend/chainlit/slack/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import httpx
from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
from slack_bolt.async_app import AsyncApp

from chainlit.config import config
Expand Down Expand Up @@ -125,6 +126,16 @@ async def update_step(self, step_dict: StepDict):
)


async def start_socket_mode():
"""
Initializes and starts the Slack app in Socket Mode asynchronously.

Uses the SLACK_WEBSOCKET_TOKEN from environment variables to authenticate.
"""
handler = AsyncSocketModeHandler(slack_app, os.environ.get("SLACK_WEBSOCKET_TOKEN"))
await handler.start_async()


def init_slack_context(
session: HTTPSession,
slack_channel_id: str,
Expand Down
54 changes: 54 additions & 0 deletions backend/tests/test_slack_socket_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# tests/test_slack_socket_mode.py
import importlib
from unittest.mock import AsyncMock, patch

import pytest


@pytest.mark.asyncio
async def test_start_socket_mode_starts_handler(monkeypatch):
"""
The function should:
• build an AsyncSocketModeHandler with the global slack_app
• use the token found in SLACK_WEBSOCKET_TOKEN
• await the handler.start_async() coroutine exactly once
"""
token = "xapp-fake-token"
# minimal env required for the Slack module to initialise
monkeypatch.setenv("SLACK_BOT_TOKEN", "xoxb-fake-bot")
monkeypatch.setenv("SLACK_WEBSOCKET_TOKEN", token)

# Import the module first to avoid lazy import registry issues
slack_app_mod = importlib.import_module("chainlit.slack.app")

# Patch the object directly instead of using string path
with patch.object(
slack_app_mod, "AsyncSocketModeHandler", autospec=True
) as handler_cls:
handler_instance = AsyncMock()
handler_cls.return_value = handler_instance

# Run: should build handler + await start_async
await slack_app_mod.start_socket_mode()

handler_cls.assert_called_once_with(slack_app_mod.slack_app, token)
handler_instance.start_async.assert_awaited_once()


def test_slack_http_route_registered(monkeypatch):
"""
When only the classic HTTP tokens are set (no websocket token),
the FastAPI app should expose POST /slack/events.
"""
# HTTP-only environment
monkeypatch.setenv("SLACK_BOT_TOKEN", "xoxb-fake-bot")
monkeypatch.setenv("SLACK_SIGNING_SECRET", "shhh-fake-secret")
monkeypatch.delenv("SLACK_WEBSOCKET_TOKEN", raising=False)

# Re-import server with the fresh env so the route table is built correctly
server = importlib.reload(importlib.import_module("chainlit.server"))

assert any(
route.path == "/slack/events" and "POST" in route.methods
for route in server.router.routes
), "Slack HTTP handler route was not registered"
Loading