Skip to content

feat(dingtalk): add proactive message API to send_message tool - #12077

Open
shuans wants to merge 1 commit into
NousResearch:mainfrom
shuans:main
Open

shuans wants to merge 1 commit into
NousResearch:mainfrom
shuans:main

Conversation

@shuans

@shuans shuans commented Apr 18, 2026

Copy link
Copy Markdown

send_message now supports sending to arbitrary DingTalk groups/users without requiring a webhook_url configuration. Uses the enterprise robot proactive message API (v1.0) when DINGTALK_CLIENT_ID and DINGTALK_CLIENT_SECRET are available.

Three-tier routing priority:

  1. Proactive API — calls /v1.0/robot/groupMessages/send (groups) or /v1.0/robot/oToMessages/batchSend (users) with access_token. chat_id is respected. Requires "robot proactive message" permission.
  2. Session webhook — looks up cached session webhook from recent inbound message (bot must have been @mentioned within ~5 minutes).
  3. Static webhook — falls back to DINGTALK_WEBHOOK_URL (default group only; chat_id ignored).

Also adds module-level shared session webhook cache (get_dingtalk_session_webhook) so the send_message tool can access session webhooks cached by the gateway adapter.

Changes:

  • gateway/platforms/dingtalk.py: Add shared cache + getter/storer functions; make adapter use shared cache; simplify _on_message store logic.
  • tools/send_message_tool.py: Rewrite _send_dingtalk() with proactive API support, session webhook lookup, and improved error messages.

User-facing:

  • hermes send_message dingtalk:cidXXXX "msg" # sends to specific group
  • hermes send_message dingtalk:user123 "msg" # sends to specific user
  • Requires DINGTALK_CLIENT_ID/SECRET (and optional DINGTALK_ROBOT_CODE).
  • webhook_url optional now; default-group-only fallback still supported.

What does this PR do?

Related Issue

Fixes #

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

How to Test

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 run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform:

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 updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — 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

For New Skills

  • This skill is broadly useful to most users (if bundled) — see Contributing Guide
  • SKILL.md follows the standard format (frontmatter, trigger conditions, steps, pitfalls)
  • No external dependencies that aren't already available (prefer stdlib, curl, existing Hermes tools)
  • I've tested the skill end-to-end: hermes --toolsets skills -q "Use the X skill to do Y"

Screenshots / Logs

send_message now supports sending to arbitrary DingTalk groups/users
without requiring a webhook_url configuration.  Uses the enterprise
robot proactive message API (v1.0) when DINGTALK_CLIENT_ID and
DINGTALK_CLIENT_SECRET are available.

Three-tier routing priority:
1. Proactive API — calls /v1.0/robot/groupMessages/send (groups) or
   /v1.0/robot/oToMessages/batchSend (users) with access_token.
   chat_id is respected. Requires "robot proactive message" permission.
2. Session webhook — looks up cached session webhook from recent inbound
   message (bot must have been @mentioned within ~5 minutes).
3. Static webhook — falls back to DINGTALK_WEBHOOK_URL (default group
   only; chat_id ignored).

Also adds module-level shared session webhook cache
(get_dingtalk_session_webhook) so the send_message tool can access
session webhooks cached by the gateway adapter.

Changes:
- gateway/platforms/dingtalk.py: Add shared cache + getter/storer functions;
  make adapter use shared cache; simplify _on_message store logic.
- tools/send_message_tool.py: Rewrite _send_dingtalk() with proactive API
  support, session webhook lookup, and improved error messages.

User-facing:
- hermes send_message dingtalk:cidXXXX "msg"  # sends to specific group
- hermes send_message dingtalk:user123 "msg" # sends to specific user
- Requires DINGTALK_CLIENT_ID/SECRET (and optional DINGTALK_ROBOT_CODE).
- webhook_url optional now; default-group-only fallback still supported.
@alt-glitch alt-glitch added type/feature New feature or request P2 Medium — degraded but workaround exists platform/dingtalk DingTalk adapter comp/gateway Gateway runner, session dispatch, delivery labels Apr 24, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

Overlaps significantly with #14336 (proactive messaging via Robot OpenAPI) and #12769 (proactive messaging + media pipeline). Recommend consolidating.

@shuans

shuans commented May 28, 2026

Copy link
Copy Markdown
Author

Hi guys, I built a CLI tool that sends messages directly to DingTalk (individual or group), with a plugin system so more platforms can be added later.
npx msgcli send "hello world"

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the proactive DingTalk delivery work. The capability is still needed: current main returns No valid session_webhook when live sends lack an inbound webhook (plugins/platforms/dingtalk/adapter.py:847-856) and its standalone sender only uses a static webhook (plugins/platforms/dingtalk/adapter.py:1527-1562).

Problems

  • The PR targets the retired inline adapter path. Commit 560010547 moved DingTalk to plugins/platforms/dingtalk/adapter.py; send_message now dispatches through the platform registry (tools/send_message_tool.py:1072). The implementation needs a plugin-layer port.
  • In the PR, token acquisition accepts DINGTALK_CLIENT_ID (tools/send_message_tool.py:1466), but robot_code does not fall back to that value (tools/send_message_tool.py:1503-1507). Environment-only users therefore skip proactive delivery unless they set another variable. Current adapter convention defaults robot code to the resolved client ID (plugins/platforms/dingtalk/adapter.py:211).
  • The PR changes only implementation files and adds no coverage for the new routing paths.

Suggested changes

  • Implement the path in the bundled DingTalk plugin's standalone_sender_fn, preserve registry dispatch, and add mocked group, OTO, session-webhook, static-webhook, and environment-only credential tests.

Automated hermes-sweeper review.

try:
robot_code = (
extra.get("robot_code")
or extra.get("client_id")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client_id above can come from DINGTALK_CLIENT_ID, but this fallback only considers extra["client_id"]. That makes the advertised environment-only setup skip the proactive call unless DINGTALK_ROBOT_CODE is also set. Reuse the resolved client_id here; current main's adapter defaults robot code to its resolved client ID.

# Tier 2: Session webhook cache (gateway inbound cache)
if chat_id:
try:
from gateway.platforms.dingtalk import get_dingtalk_session_webhook

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main moved DingTalk into plugins/platforms/dingtalk/adapter.py and routes send_message through its registered standalone sender. Port this lookup/delivery path to the bundled plugin rather than importing the removed gateway.platforms.dingtalk module.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 12, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary

Fourteen PRs address or reference the DingTalk cluster: proactive delivery, outbound and inbound media, quoted replies, card throttling, session reliability, and deployment configuration. The diffs range from broad stacked or umbrella implementations (#12769, #14333#14336, #33899) to focused slices (#12077, #17365, #17367#17371, #40929), but the active adapter and standalone sender now live under the DingTalk plugin contract, so the reusable changes require targeted plugin-layer ports.

Related pull requests

Duplicates

#14333 and #40929 are the same reliability bundle, with #14333 closed as superseded by #40929; #14334/#17368 cover quoted replies, #14335/#17369 cover inbound media, #14336/#17370/#17371 cover proactive and outbound-media delivery, and #17365 extracts #14333’s throttle. #12769 is the umbrella containing most of those slices, while closed #33899 is a duplicate of #14336 and #12077 overlaps the proactive-delivery portion.

Suggested consolidation

Author action: rebase onto main, or split out the part that can merge—use #12769 as the umbrella, but separate current-plugin PRs for proactive live plus standalone delivery (#12077/#14336/#17370), quoted replies (#14334/#17368), bounded inbound media (#14335/#17369), and complete card throttling (#17365/#40929), while removing the core routing and guard-script changes. This preserves the explicit keep_open and maintainer-bot salvage verdicts and does not cross #12769’s CHANGES_REQUESTED review; keep closed #14333 and #33899 closed, and close the remaining duplicate branches only after their identified plugin-layer slices and tests have been consolidated.

Cross-PR triage: Reviewed 14 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 418 kB of PR diffs, 31 kB of issue/PR text, 33 kB of discussion (42 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/dingtalk DingTalk adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants