Skip to content

slack_daemon

slack_daemon

Slack Socket Mode daemon — listens for DMs and responds with DeepResearch.

Run as a standalone process or import start_slack_daemon() to spawn from the server. Uses slack-bolt for reliable Socket Mode handling.

Functions

run_slack_daemon

run_slack_daemon(bot_token: str, app_token: str, model: str = 'qwen3.5:9b') -> None

Run the Slack daemon (blocking). Handles DMs with DeepResearch.

Source code in src/openjarvis/channels/slack_daemon.py
def run_slack_daemon(
    bot_token: str,
    app_token: str,
    model: str = "qwen3.5:9b",
) -> None:
    """Run the Slack daemon (blocking). Handles DMs with DeepResearch."""
    import threading

    from slack_bolt import App
    from slack_bolt.adapter.socket_mode import SocketModeHandler

    from openjarvis.agents.deep_research import DeepResearchAgent
    from openjarvis.engine.ollama import OllamaEngine
    from openjarvis.server.agent_manager_routes import (
        _build_deep_research_tools,
    )

    # Write PID
    pid_path = Path(_PID_FILE)
    pid_path.parent.mkdir(parents=True, exist_ok=True)
    pid_path.write_text(str(os.getpid()))

    # Build agent
    engine = OllamaEngine()
    tools = _build_deep_research_tools(engine=engine, model=model)
    agent = DeepResearchAgent(
        engine=engine, model=model, tools=tools, max_turns=5,
    )
    logger.info("Slack daemon: agent ready with %d tools", len(tools))

    app = App(token=bot_token)

    processing = threading.Event()

    @app.event("message")
    def handle_dm(event: dict, say: Any) -> None:
        text = event.get("text", "")
        if not text:
            return

        ts = event.get("ts", "")
        logger.info("Slack DM: %s", text[:60])

        say(text="Message received! Working on it now...", thread_ts=ts)
        processing.set()

        # Progress updater
        stop = threading.Event()

        def _progress() -> None:
            while not stop.is_set():
                stop.wait(60)
                if stop.is_set():
                    break
                if processing.is_set():
                    say(
                        text="Still working! Will reply ASAP",
                        thread_ts=ts,
                    )

        pt = threading.Thread(target=_progress, daemon=True)
        pt.start()

        try:
            result = agent.run(text)
            reply = _to_slack_fmt(result.content or "No results found.")
        except Exception as exc:
            reply = f"Error: {exc}"
            logger.error("Slack daemon error: %s", exc)
        finally:
            processing.clear()
            stop.set()

        say(text=reply, thread_ts=ts)
        logger.info("Slack DM reply sent (%d chars)", len(reply))

    handler = SocketModeHandler(app, app_token)

    # Graceful shutdown
    def _stop(signum: int, frame: Any) -> None:
        logger.info("Slack daemon stopping...")
        handler.close()
        if pid_path.exists():
            pid_path.unlink()
        sys.exit(0)

    signal.signal(signal.SIGTERM, _stop)
    signal.signal(signal.SIGINT, _stop)

    logger.info("Slack daemon started, listening for DMs...")
    handler.start()  # Blocks until close()

start_slack_daemon

start_slack_daemon(bot_token: str, app_token: str, model: str = 'qwen3.5:9b') -> int

Spawn the Slack daemon as a background subprocess.

Returns the PID of the spawned process.

Source code in src/openjarvis/channels/slack_daemon.py
def start_slack_daemon(
    bot_token: str,
    app_token: str,
    model: str = "qwen3.5:9b",
) -> int:
    """Spawn the Slack daemon as a background subprocess.

    Returns the PID of the spawned process.
    """
    import subprocess

    proc = subprocess.Popen(
        [
            sys.executable, "-m",
            "openjarvis.channels.slack_daemon",
            "--bot-token", bot_token,
            "--app-token", app_token,
            "--model", model,
        ],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        start_new_session=True,
    )
    return proc.pid

is_running

is_running() -> bool

Check if the Slack daemon is running.

Source code in src/openjarvis/channels/slack_daemon.py
def is_running() -> bool:
    """Check if the Slack daemon is running."""
    pid_path = Path(_PID_FILE)
    if not pid_path.exists():
        return False
    try:
        pid = int(pid_path.read_text().strip())
        os.kill(pid, 0)
        return True
    except (ValueError, ProcessLookupError, PermissionError):
        pid_path.unlink(missing_ok=True)
        return False

stop_daemon

stop_daemon() -> bool

Stop the running Slack daemon.

Source code in src/openjarvis/channels/slack_daemon.py
def stop_daemon() -> bool:
    """Stop the running Slack daemon."""
    pid_path = Path(_PID_FILE)
    if not pid_path.exists():
        return False
    try:
        pid = int(pid_path.read_text().strip())
        os.kill(pid, signal.SIGTERM)
        pid_path.unlink(missing_ok=True)
        return True
    except (ValueError, ProcessLookupError, PermissionError):
        pid_path.unlink(missing_ok=True)
        return False