feat: add IM channels for Feishu, Slack, and Telegram (#1010)
* feat: add IM channels system for Feishu, Slack, and Telegram integration
Bridge external messaging platforms to DeerFlow via LangGraph Server with
async message bus, thread management, and per-channel configuration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review comments on IM channels system
Fix topic_id handling in store remove/list_entries and manager commands,
correct Telegram reply threading, remove unused imports/variables, update
docstrings and docs to match implementation, and prevent config mutation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update skill creator
* fix im reply text
* fix comments
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:21:18 +08:00
|
|
|
"""Abstract base class for IM channels."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
from abc import ABC, abstractmethod
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
refactor: split backend into harness (deerflow.*) and app (app.*) (#1131)
* refactor: extract shared utils to break harness→app cross-layer imports
Move _validate_skill_frontmatter to src/skills/validation.py and
CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py.
This eliminates the two reverse dependencies from client.py (harness layer)
into gateway/routers/ (app layer), preparing for the harness/app package split.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split backend/src into harness (deerflow.*) and app (app.*)
Physically split the monolithic backend/src/ package into two layers:
- **Harness** (`packages/harness/deerflow/`): publishable agent framework
package with import prefix `deerflow.*`. Contains agents, sandbox, tools,
models, MCP, skills, config, and all core infrastructure.
- **App** (`app/`): unpublished application code with import prefix `app.*`.
Contains gateway (FastAPI REST API) and channels (IM integrations).
Key changes:
- Move 13 harness modules to packages/harness/deerflow/ via git mv
- Move gateway + channels to app/ via git mv
- Rename all imports: src.* → deerflow.* (harness) / app.* (app layer)
- Set up uv workspace with deerflow-harness as workspace member
- Update langgraph.json, config.example.yaml, all scripts, Docker files
- Add build-system (hatchling) to harness pyproject.toml
- Add PYTHONPATH=. to gateway startup commands for app.* resolution
- Update ruff.toml with known-first-party for import sorting
- Update all documentation to reflect new directory structure
Boundary rule enforced: harness code never imports from app.
All 429 tests pass. Lint clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add harness→app boundary check test and update docs
Add test_harness_boundary.py that scans all Python files in
packages/harness/deerflow/ and fails if any `from app.*` or
`import app.*` statement is found. This enforces the architectural
rule that the harness layer never depends on the app layer.
Update CLAUDE.md to document the harness/app split architecture,
import conventions, and the boundary enforcement test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add config versioning with auto-upgrade on startup
When config.example.yaml schema changes, developers' local config.yaml
files can silently become outdated. This adds a config_version field and
auto-upgrade mechanism so breaking changes (like src.* → deerflow.*
renames) are applied automatically before services start.
- Add config_version: 1 to config.example.yaml
- Add startup version check warning in AppConfig.from_file()
- Add scripts/config-upgrade.sh with migration registry for value replacements
- Add `make config-upgrade` target
- Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services
- Add config error hints in service failure messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix comments
* fix: update src.* import in test_sandbox_tools_security to deerflow.*
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle empty config and search parent dirs for config.example.yaml
Address Copilot review comments on PR #1131:
- Guard against yaml.safe_load() returning None for empty config files
- Search parent directories for config.example.yaml instead of only
looking next to config.yaml, fixing detection in common setups
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct skills root path depth and config_version type coercion
- loader.py: fix get_skills_root_path() to use 5 parent levels (was 3)
after harness split, file lives at packages/harness/deerflow/skills/
so parent×3 resolved to backend/packages/harness/ instead of backend/
- app_config.py: coerce config_version to int() before comparison in
_check_config_version() to prevent TypeError when YAML stores value
as string (e.g. config_version: "1")
- tests: add regression tests for both fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update test imports from src.* to deerflow.*/app.* after harness refactor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 22:55:52 +08:00
|
|
|
from app.channels.message_bus import InboundMessage, InboundMessageType, MessageBus, OutboundMessage, ResolvedAttachment
|
feat: add IM channels for Feishu, Slack, and Telegram (#1010)
* feat: add IM channels system for Feishu, Slack, and Telegram integration
Bridge external messaging platforms to DeerFlow via LangGraph Server with
async message bus, thread management, and per-channel configuration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review comments on IM channels system
Fix topic_id handling in store remove/list_entries and manager commands,
correct Telegram reply threading, remove unused imports/variables, update
docstrings and docs to match implementation, and prevent config mutation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update skill creator
* fix im reply text
* fix comments
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:21:18 +08:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Channel(ABC):
|
|
|
|
|
"""Base class for all IM channel implementations.
|
|
|
|
|
|
|
|
|
|
Each channel connects to an external messaging platform and:
|
|
|
|
|
1. Receives messages, wraps them as InboundMessage, publishes to the bus.
|
|
|
|
|
2. Subscribes to outbound messages and sends replies back to the platform.
|
|
|
|
|
|
|
|
|
|
Subclasses must implement ``start``, ``stop``, and ``send``.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, name: str, bus: MessageBus, config: dict[str, Any]) -> None:
|
|
|
|
|
self.name = name
|
|
|
|
|
self.bus = bus
|
|
|
|
|
self.config = config
|
|
|
|
|
self._running = False
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def is_running(self) -> bool:
|
|
|
|
|
return self._running
|
|
|
|
|
|
|
|
|
|
# -- lifecycle ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
async def start(self) -> None:
|
|
|
|
|
"""Start listening for messages from the external platform."""
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
async def stop(self) -> None:
|
|
|
|
|
"""Gracefully stop the channel."""
|
|
|
|
|
|
|
|
|
|
# -- outbound ----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
async def send(self, msg: OutboundMessage) -> None:
|
|
|
|
|
"""Send a message back to the external platform.
|
|
|
|
|
|
|
|
|
|
The implementation should use ``msg.chat_id`` and ``msg.thread_ts``
|
|
|
|
|
to route the reply to the correct conversation/thread.
|
|
|
|
|
"""
|
|
|
|
|
|
2026-03-10 09:11:57 +08:00
|
|
|
async def send_file(self, msg: OutboundMessage, attachment: ResolvedAttachment) -> bool:
|
|
|
|
|
"""Upload a single file attachment to the platform.
|
|
|
|
|
|
|
|
|
|
Returns True if the upload succeeded, False otherwise.
|
|
|
|
|
Default implementation returns False (no file upload support).
|
|
|
|
|
"""
|
|
|
|
|
return False
|
|
|
|
|
|
feat: add IM channels for Feishu, Slack, and Telegram (#1010)
* feat: add IM channels system for Feishu, Slack, and Telegram integration
Bridge external messaging platforms to DeerFlow via LangGraph Server with
async message bus, thread management, and per-channel configuration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review comments on IM channels system
Fix topic_id handling in store remove/list_entries and manager commands,
correct Telegram reply threading, remove unused imports/variables, update
docstrings and docs to match implementation, and prevent config mutation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update skill creator
* fix im reply text
* fix comments
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:21:18 +08:00
|
|
|
# -- helpers -----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def _make_inbound(
|
|
|
|
|
self,
|
|
|
|
|
chat_id: str,
|
|
|
|
|
user_id: str,
|
|
|
|
|
text: str,
|
|
|
|
|
*,
|
|
|
|
|
msg_type: InboundMessageType = InboundMessageType.CHAT,
|
|
|
|
|
thread_ts: str | None = None,
|
|
|
|
|
files: list[dict[str, Any]] | None = None,
|
|
|
|
|
metadata: dict[str, Any] | None = None,
|
|
|
|
|
) -> InboundMessage:
|
|
|
|
|
"""Convenience factory for creating InboundMessage instances."""
|
|
|
|
|
return InboundMessage(
|
|
|
|
|
channel_name=self.name,
|
|
|
|
|
chat_id=chat_id,
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
text=text,
|
|
|
|
|
msg_type=msg_type,
|
|
|
|
|
thread_ts=thread_ts,
|
|
|
|
|
files=files or [],
|
|
|
|
|
metadata=metadata or {},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def _on_outbound(self, msg: OutboundMessage) -> None:
|
|
|
|
|
"""Outbound callback registered with the bus.
|
|
|
|
|
|
|
|
|
|
Only forwards messages targeted at this channel.
|
2026-03-10 09:11:57 +08:00
|
|
|
Sends the text message first, then uploads any file attachments.
|
|
|
|
|
File uploads are skipped entirely when the text send fails to avoid
|
|
|
|
|
partial deliveries (files without accompanying text).
|
feat: add IM channels for Feishu, Slack, and Telegram (#1010)
* feat: add IM channels system for Feishu, Slack, and Telegram integration
Bridge external messaging platforms to DeerFlow via LangGraph Server with
async message bus, thread management, and per-channel configuration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review comments on IM channels system
Fix topic_id handling in store remove/list_entries and manager commands,
correct Telegram reply threading, remove unused imports/variables, update
docstrings and docs to match implementation, and prevent config mutation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update skill creator
* fix im reply text
* fix comments
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:21:18 +08:00
|
|
|
"""
|
|
|
|
|
if msg.channel_name == self.name:
|
|
|
|
|
try:
|
|
|
|
|
await self.send(msg)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.exception("Failed to send outbound message on channel %s", self.name)
|
2026-03-10 09:11:57 +08:00
|
|
|
return # Do not attempt file uploads when the text message failed
|
|
|
|
|
|
|
|
|
|
for attachment in msg.attachments:
|
|
|
|
|
try:
|
|
|
|
|
success = await self.send_file(msg, attachment)
|
|
|
|
|
if not success:
|
|
|
|
|
logger.warning("[%s] file upload skipped for %s", self.name, attachment.filename)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.exception("[%s] failed to upload file %s", self.name, attachment.filename)
|