Skip to content

feat(dingtalk): add QR code auth support and fix 3 critical adapter bugs#8345

Closed
meng93 wants to merge 1 commit into
NousResearch:mainfrom
meng93:feat/dingtalk-qrcode
Closed

feat(dingtalk): add QR code auth support and fix 3 critical adapter bugs#8345
meng93 wants to merge 1 commit into
NousResearch:mainfrom
meng93:feat/dingtalk-qrcode

Conversation

@meng93

@meng93 meng93 commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix three critical bugs in the DingTalk platform adapter that prevented the bot from receiving and replying to messages, and add QR code scanning authorization to hermes gateway setup for a streamlined onboarding experience.

Motivation

Bug Fixes

The existing DingTalk adapter had three issues that made it non-functional in production:

  1. Stream connection method errorDingTalkStreamClient.start() is an async coroutine, but the code wrapped it with asyncio.to_thread() as if it were blocking, causing callbacks to execute in the wrong thread and never dispatch to the main event loop.
  2. Message field parsing failure — The DingTalk Stream SDK delivers CallbackMessage objects whose business fields (senderId, text, sessionWebhook, etc.) live inside message.data (camelCase keys), not as top-level Python attributes. The original code used getattr(message, "sender_id") which always returned None.
  3. Webhook URL validation miss — The SSRF guard regex only matched api.dingtalk.com, but DingTalk actually returns session webhooks on oapi.dingtalk.com, causing all reply attempts to be rejected.

New Feature — QR Code Auth

Currently, setting up DingTalk requires users to:

  1. Go to the DingTalk Open Platform developer console
  2. Create/find their application
  3. Manually copy the Client ID and Client Secret
  4. Paste them into the CLI setup wizard

This is error-prone and unfriendly for new users. The DingTalk Open Platform provides a Device Flow OAuth registration API that allows CLI tools to obtain credentials via QR code scanning — no pre-existing app credentials needed.

Changes

Modified: gateway/platforms/dingtalk.py

Bug 1: Stream client async handling

- await asyncio.to_thread(self._stream_client.start)
+ # DingTalkStreamClient.start() is an async coroutine — await it directly
+ await self._stream_client.start()
- def process(self, message: "ChatbotMessage"):
-     future = asyncio.run_coroutine_threadsafe(self._adapter._on_message(message), loop)
-     future.result(timeout=60)
+ async def process(self, message: "ChatbotMessage"):
+     await self._adapter._on_message(message)

Bug 2: Message field extraction from CallbackMessage

- msg_id = getattr(message, "message_id", None) or uuid.uuid4().hex
- sender_id = getattr(message, "sender_id", "") or ""
- session_webhook = getattr(message, "session_webhook", None) or ""
+ data: Dict[str, Any] = getattr(message, "data", None) or {}
+ if isinstance(data, str):
+     data = json.loads(data)
+ msg_id = data.get("msgId") or getattr(message, "message_id", None) or uuid.uuid4().hex
+ sender_id = data.get("senderId", "") or ""
+ session_webhook = data.get("sessionWebhook", "") or ""

Bug 3: Webhook URL domain validation

- _DINGTALK_WEBHOOK_RE = re.compile(r'^https://api\.dingtalk\.com/')
+ _DINGTALK_WEBHOOK_RE = re.compile(r'^https://(?:api|oapi)\.dingtalk\.com/')

Modified: gateway/config.py

  • Added DINGTALK_CLIENT_ID / DINGTALK_CLIENT_SECRET / DINGTALK_HOME_CHANNEL environment variable handling in _apply_env_overrides(), consistent with existing platform patterns (Feishu, Slack, etc.)

New file: hermes_cli/dingtalk_auth.py

  • Implements the DingTalk Device Flow OAuth registration (init → begin → poll)
  • Renders a compact QR code directly in the terminal using half-block characters (▀▄█)
  • Polls for authorization completion with configurable timeout (default 2 hours)
  • Auto-installs qrcode dependency on first use; falls back gracefully if unavailable

Modified: hermes_cli/gateway.py

  • Rewrote _setup_dingtalk() to offer two setup methods:
    • QR code scan (recommended) — auto-obtains Client ID & Client Secret
    • Manual input — existing behavior preserved as fallback
  • Registered _setup_dingtalk() in the platform selection switch in gateway_setup()

User Experience

When selecting DingTalk in hermes gateway setup:

─── 💬 DingTalk Setup ───

Choose setup method
→ (●) QR Code Scan (Recommended, auto-obtain Client ID and Client Secret)
  (○) Manual Input (Client ID and Client Secret)

After selecting QR scan, a QR code is rendered in the terminal. The user scans it with DingTalk, and credentials are automatically saved.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)

How to Test

QR Code Auth

  1. Run hermes gateway setup, select DingTalk → QR Code Scan
  2. Scan the terminal QR code with DingTalk app
  3. Verify credentials are automatically saved to .env
  4. Cancel mid-scan and verify graceful fallback to manual input

Bug Fixes

  1. Configure DINGTALK_CLIENT_ID and DINGTALK_CLIENT_SECRET in .env
  2. Run hermes gateway and confirm log output: [dingtalk] Connected via Stream Mode
  3. Send a message to the bot in DingTalk and verify:
    • Log shows session_webhook=https://oapi.dingtalk.com/... (not EMPTY)
    • Bot replies normally (no "No session_webhook available" error)

Dependencies

  • qrcode (optional, auto-installed on first use, graceful fallback to manual input if unavailable)

Screenshots / Logs

Before fix:

[dingtalk] session_webhook=EMPTY chat_id=EMPTY match=False
❌ No session_webhook available. Reply must follow an incoming message.

After fix:

[dingtalk] session_webhook=https://oapi.dingtalk.com/robot/sendBySession... chat_id=cidiwaOZiGz... match=True
[dingtalk] Message from user in cidiwaOZiGzOR...: hello

Breaking Changes

None. All changes are backward-compatible:

  • Existing manual DingTalk setup still works as before
  • qrcode dependency is optional with graceful fallback
  • No config format changes

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've tested on my platform: macOS (Darwin 24.5.0)
  • End-to-end verified in production environment

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

@meng93 meng93 force-pushed the feat/dingtalk-qrcode branch from dfe71fe to e2ac58c Compare April 13, 2026 04:51
- feat: support one-click QR scan to create DingTalk bot and establish connection
- fix(gateway): wrap blocking DingTalkStreamClient.start() with asyncio.to_thread()
- fix(gateway): extract message fields from CallbackMessage payload instead of ChatbotMessage
- fix(gateway): add oapi.dingtalk.com to allowed webhook URL domains
@meng93 meng93 force-pushed the feat/dingtalk-qrcode branch from e2ac58c to 0aac921 Compare April 13, 2026 04:57
@meng93 meng93 changed the title feat(dingtalk): add QR code auth support in gateway setup feat(dingtalk): add QR code auth support and fix 3 critical adapter bugs Apr 13, 2026
@chenweigao

Copy link
Copy Markdown

looks good for my bug: #9752

@teknium1

Copy link
Copy Markdown
Contributor

Merged via #11574 (#11574). Your commit was cherry-picked with authorship preserved (9deeee7). Thanks for building the QR flow — it shipped essentially as you wrote it, with one small follow-up: added a user-facing disclosure that the scan page is OpenClaw-branded, since DingTalk's registration portal routes every source to /openapp/registration/openClaw regardless of the source parameter value we send.

What I dropped from this PR: the gateway/platforms/dingtalk.py SDK compatibility fixes (async process(), CallbackMessage.data parsing, oapi.dingtalk.com webhook regex) — those landed on main separately via #11471 before this salvage, so the cherry-pick conflict was resolved by keeping the current main version.

Also added 15 regression tests covering _api_post, begin_registration, the polling loop, and the QR renderer.

Follow-up: Teknium will reach out to DingTalk-Real-AI via Nous's Alibaba channel to register a sanctioned hermes source token. When approved, the DINGTALK_REGISTRATION_SOURCE default will flip from openClaw to hermes and the disclosure will come out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants