一键导入
openclaw-dingtalk-channel
DingTalk channel plugin for OpenClaw that enables enterprise bot integration using Stream mode without public IP requirements
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
DingTalk channel plugin for OpenClaw that enables enterprise bot integration using Stream mode without public IP requirements
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Browser-based interface for viewing and filtering OpenClaw session tool call history with zero dependencies for local network deployment.
AI-powered quantitative research and backtesting platform with end-to-end workflow from research to strategy publication
Give your AI assistant a phone — OpenClaw plugin for real phone calls via Twilio + OpenAI Realtime API with in-call tools, transcripts, and call screening
Run multi-model consensus panels (Lite or Heavy) with your own agent backends—no hosted middleware, your models, your rules.
Build a multi-role JARVIS-style voice assistant with local ASR/TTS, OpenClaw LLM gateway, voice wake words, HUD effects, and speaker verification
Use 37 battle-tested marketing skills covering CRO, copywriting, SEO, paid ads, email, growth, and strategy with real data connectors for Google Ads, Search Console, Meta Ads, and X/Twitter
| name | openclaw-dingtalk-channel |
| description | DingTalk channel plugin for OpenClaw that enables enterprise bot integration using Stream mode without public IP requirements |
| triggers | ["how do I set up the DingTalk channel for OpenClaw","configure OpenClaw with DingTalk enterprise bot","install the dingtalk plugin for openclaw","set up AI card streaming in DingTalk","configure multi-agent routing in DingTalk channel","troubleshoot OpenClaw DingTalk connection","implement btw bypass mode in DingTalk","handle DingTalk message types in OpenClaw"] |
Skill by ara.so — Hermes Skills collection.
This skill provides expertise in configuring and using the @soimy/dingtalk channel plugin for OpenClaw, which enables DingTalk enterprise bot integration with Stream mode (no public IP required).
The DingTalk channel plugin connects OpenClaw to DingTalk enterprise bots, supporting:
@agent-name routing/btw for quick answers outside main session lockRequires OpenClaw >= 2026.3.24.
openclaw plugins install @soimy/dingtalk
git clone https://github.com/soimy/openclaw-channel-dingtalk.git
cd openclaw-channel-dingtalk
npm install
openclaw plugins install -l .
Add to OpenClaw configuration:
{
"plugins": {
"enabled": true,
"allow": ["dingtalk"]
}
}
openclaw onboard
Or configure channels section:
openclaw configure --section channels
{
"channels": {
"dingtalk": {
"enabled": true,
"clientId": "dingxxxxxx",
"clientSecret": "your-app-secret",
"dmPolicy": "open",
"groupPolicy": "open",
"messageType": "markdown"
}
}
}
{
"channels": {
"dingtalk": {
"enabled": true,
"clientId": process.env.DINGTALK_CLIENT_ID,
"clientSecret": process.env.DINGTALK_CLIENT_SECRET,
// Access policies
"dmPolicy": "open", // "open" | "whitelist" | "blacklist" | "closed"
"groupPolicy": "open", // Same options
"dmWhitelist": [], // User IDs if dmPolicy is "whitelist"
"groupWhitelist": [], // Conversation IDs if groupPolicy is "whitelist"
// Message type: "markdown" or "aiCard"
"messageType": "aiCard",
// AI Card streaming options
"aiCardStreamingMode": true,
"aiCardStreamingInterval": 800, // ms between chunks
// Multi-agent bindings
"agentBindings": [
{
"agentName": "default",
"robotCode": "dingxxxxxx",
"isDefault": true
},
{
"agentName": "analyst",
"robotCode": "dingyyyyyyy",
"isDefault": false
}
],
// Routing
"enableAtAgentRouting": true,
"atAgentRoutingPrefix": "@",
// Features
"enableBtwBypass": true,
"enableRealTimeAbort": true,
"abortKeywords": ["停止", "stop", "/stop", "esc"],
// Security
"maxMessageLength": 10000,
"rateLimitPerUser": 20,
"rateLimitWindow": 60000 // 1 minute
}
}
}
export DINGTALK_CLIENT_ID="dingxxxxxx"
export DINGTALK_CLIENT_SECRET="your-app-secret"
In DingTalk Developer Console, configure:
qyapi_robot_sendmsg - Send messagesqyapi_chat_manage - Group chat managementcontact.User.Read - Read user infoim.Chat.Read - Read conversation infochat_update_title - Group name changeschat_update_owner - Owner changesim_robot_at_message - @ mentionsim_robot_message - Direct messagesSimple text responses with Markdown formatting:
{
"messageType": "markdown"
}
User sees plain Markdown-formatted text.
Rich interactive cards with streaming support:
{
"messageType": "aiCard",
"aiCardStreamingMode": true,
"aiCardStreamingInterval": 800
}
Features:
Quick answers outside the main session lock:
User: /btw What's the weather?
The message bypasses conversation locking and gets immediate response in a separate session. Prefix is configurable:
{
"enableBtwBypass": true,
"btwPrefix": "/btw" // Optional, defaults to "/btw"
}
Bind multiple OpenClaw agents to different DingTalk bots:
{
"agentBindings": [
{
"agentName": "default",
"robotCode": "dingbot001",
"isDefault": true
},
{
"agentName": "analyst",
"robotCode": "dingbot002",
"isDefault": false
},
{
"agentName": "writer",
"robotCode": "dingbot003",
"isDefault": false
}
]
}
Route to specific agents by mentioning them:
User: @analyst analyze Q4 revenue
{
"enableAtAgentRouting": true,
"atAgentRoutingPrefix": "@"
}
The message routes to the "analyst" agent if bound.
Stop AI generation mid-stream:
User: stop or 停止 or esc
{
"enableRealTimeAbort": true,
"abortKeywords": ["停止", "stop", "/stop", "esc"]
}
Plugin automatically handles:
import type { ChannelPlugin, InboundMessage, OutboundMessage } from '@openclaw/types';
export class DingTalkChannel implements ChannelPlugin {
name = 'dingtalk';
async processInbound(rawMessage: any): Promise<InboundMessage> {
const { msgtype, text, senderStaffId, conversationId } = rawMessage;
return {
channelId: 'dingtalk',
userId: senderStaffId,
conversationId,
content: {
type: 'text',
text: text?.content || ''
},
metadata: {
msgId: rawMessage.msgId,
timestamp: rawMessage.createAt
}
};
}
async processOutbound(message: OutboundMessage): Promise<void> {
const { conversationId, content, metadata } = message;
if (this.config.messageType === 'aiCard') {
await this.sendAICard(conversationId, content, metadata);
} else {
await this.sendMarkdown(conversationId, content);
}
}
private async sendAICard(
conversationId: string,
content: any,
metadata?: any
): Promise<void> {
const cardData = {
cardTemplateId: 'standard',
outTrackId: metadata?.trackId,
cardData: {
cardParamMap: {
content: content.text,
blocks: this.formatBlocks(content)
}
}
};
await this.client.sendInteractiveCard({
conversationId,
...cardData
});
}
}
async streamAIResponse(
conversationId: string,
generator: AsyncGenerator<string>
): Promise<void> {
let buffer = '';
let lastUpdate = 0;
const interval = this.config.aiCardStreamingInterval || 800;
for await (const chunk of generator) {
buffer += chunk;
const now = Date.now();
if (now - lastUpdate >= interval) {
await this.updateAICard(conversationId, buffer);
lastUpdate = now;
}
}
// Final update
await this.updateAICard(conversationId, buffer, { finished: true });
}
function resolveAgent(message: InboundMessage): string {
const { content, metadata } = message;
// Check for @agent mention
const atMatch = content.text.match(/@(\w+)\s/);
if (atMatch) {
const agentName = atMatch[1];
const binding = this.config.agentBindings.find(
b => b.agentName === agentName
);
if (binding) return agentName;
}
// Check robot code mapping
const robotCode = metadata.robotCode;
const binding = this.config.agentBindings.find(
b => b.robotCode === robotCode
);
return binding?.agentName || 'default';
}
function isBtwMessage(text: string): boolean {
const prefix = this.config.btwPrefix || '/btw';
return text.trim().startsWith(prefix);
}
async processBtwMessage(message: InboundMessage): Promise<void> {
const prefix = this.config.btwPrefix || '/btw';
const cleanText = message.content.text.replace(prefix, '').trim();
// Create isolated session
const btwSession = await this.createBypassSession(message.userId);
// Process independently
const response = await this.agent.chat(cleanText, {
sessionId: btwSession.id,
bypass: true
});
await this.send(message.conversationId, response);
}
function checkAccess(
policy: 'open' | 'whitelist' | 'blacklist' | 'closed',
identifier: string,
whitelist: string[],
blacklist: string[]
): boolean {
switch (policy) {
case 'closed':
return false;
case 'whitelist':
return whitelist.includes(identifier);
case 'blacklist':
return !blacklist.includes(identifier);
case 'open':
default:
return true;
}
}
// Usage
const canDM = checkAccess(
config.dmPolicy,
message.userId,
config.dmWhitelist,
config.dmBlacklist
);
const canGroup = checkAccess(
config.groupPolicy,
message.conversationId,
config.groupWhitelist,
config.groupBlacklist
);
class RateLimiter {
private counts = new Map<string, number[]>();
check(userId: string, limit: number, window: number): boolean {
const now = Date.now();
const timestamps = this.counts.get(userId) || [];
// Remove old timestamps
const recent = timestamps.filter(t => now - t < window);
if (recent.length >= limit) {
return false;
}
recent.push(now);
this.counts.set(userId, recent);
return true;
}
}
// Usage
if (!this.rateLimiter.check(
message.userId,
config.rateLimitPerUser,
config.rateLimitWindow
)) {
throw new Error('Rate limit exceeded');
}
async sendWithRetry(
conversationId: string,
content: any,
maxRetries = 3
): Promise<void> {
for (let i = 0; i < maxRetries; i++) {
try {
await this.send(conversationId, content);
return;
} catch (error) {
if (i === maxRetries - 1) throw error;
// Exponential backoff
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
}
Check plugin allowlist:
openclaw config get plugins.allow
Should include "dingtalk".
Verify installation:
openclaw plugins list
Check Stream connection:
openclaw gateway status
Verify credentials:
openclaw config get channels.dingtalk.clientId
openclaw config get channels.dingtalk.clientSecret
Test DingTalk app permissions in Developer Console → Application Details → Permissions.
Enable streaming mode:
{
"messageType": "aiCard",
"aiCardStreamingMode": true
}
Check interval (minimum 500ms recommended):
{
"aiCardStreamingInterval": 800
}
Increase limits (if quota allows):
{
"rateLimitPerUser": 30,
"rateLimitWindow": 60000
}
Check DingTalk quota in Developer Console → Resource Management.
Verify bindings:
openclaw config get channels.dingtalk.agentBindings
Check agent names match:
openclaw agents list
Enable routing:
{
"enableAtAgentRouting": true
}
If auto-registration fails:
clientId and clientSecretopenclaw logs --filter dingtalkopenclaw plugins update dingtalk
cd openclaw-channel-dingtalk
git pull
openclaw gateway restart
2026.3.24