@dingtalk-real-ai/dingtalk-connector 0.8.19-beta.3 → 0.8.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.en.md +3 -3
- package/README.md +4 -4
- package/bin/dingtalk-connector.js +102 -28
- package/dist/{connection-JLte7CFe.mjs → connection-BZd5NXuh.mjs} +1 -0
- package/dist/entry-bundled.mjs +1 -1
- package/dist/{game-xiyou-0JTKCTX8.mjs → game-xiyou-DxRHjOIJ.mjs} +1 -1
- package/dist/{gateway-methods-NC5q7QDF.mjs → gateway-methods-DI8lkjSd.mjs} +164 -13
- package/dist/gateway-methods-DtdiDpYK.mjs +2 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +36 -2
- package/dist/media-DEuF7r3G.mjs +2 -0
- package/dist/{message-handler-HFi22Ru-.mjs → message-handler-Bykth_t8.mjs} +62 -32
- package/dist/{messaging-CN_wQxw1.mjs → messaging-CyIJY4h2.mjs} +318 -50
- package/dist/{runtime-CDlbXD8t.mjs → runtime-D35JIkCZ.mjs} +26 -5
- package/docs/MULTI_AGENT_SETUP.md +306 -0
- package/docs/RELEASE_NOTES_V0.8.19.md +63 -0
- package/index.ts +46 -2
- package/openclaw.plugin.json +23 -1
- package/package.json +1 -1
- package/src/channel.ts +1 -0
- package/src/config/schema.ts +24 -0
- package/src/core/connection.ts +10 -0
- package/src/core/message-handler.ts +44 -21
- package/src/game-xiyou/storage.ts +1 -1
- package/src/gateway-methods.ts +216 -11
- package/src/reply-dispatcher.ts +55 -17
- package/src/sdk/types.ts +6 -0
- package/src/services/messaging/card.ts +106 -43
- package/src/services/messaging/index.ts +1 -0
- package/src/services/messaging/mentions.ts +267 -0
- package/src/services/messaging.ts +185 -20
- package/dist/gateway-methods-D1IJm1C1.mjs +0 -2
- package/dist/media-BYr7vxwA.mjs +0 -2
- package/docs/AGENT_ROUTING.md +0 -335
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.8.19] - 2026-04-25
|
|
9
|
+
|
|
10
|
+
### 新增 / Added
|
|
11
|
+
- 🔔 **DING 消息** — 支持向用户/群发送强提醒 DING(应用内/短信/电话),用户授权后即可使用
|
|
12
|
+
- 📄 **钉钉文档** — 支持创建、追加、搜索、列举钉钉文档,用户授权后即可使用
|
|
13
|
+
- 📝 **日志** — 支持提交日报/周报、查询历史日志记录,用户授权后即可使用
|
|
14
|
+
- ✨ **插件重复加载检测** — 全局 Symbol 自检同一 plugin id 多路径加载,防止 stream 回调冲突
|
|
15
|
+
|
|
16
|
+
### 修复 / Fixes
|
|
17
|
+
- 🐛 AI Card QPS 限流不再误报用户错误,改为 warn 日志
|
|
18
|
+
- 🐛 AI Card 令牌桶新增串行化锁,修复并发击穿
|
|
19
|
+
- 🐛 多 Agent 配置检测改为 OR 条件,放宽触发保护
|
|
20
|
+
- 🐛 CLI 提示文案统一中英文混合格式
|
|
21
|
+
|
|
22
|
+
### 改进 / Improvements
|
|
23
|
+
- ✅ Gateway Methods 新增 @mention 参数(`atDingtalkIds` / `atUserIds` / `atAccountIds` / `atAll`)
|
|
24
|
+
- ✅ 群聊排队 ACK 适配 `groupReplyMode`,text/markdown 模式不再创建 AI Card
|
|
25
|
+
- ✅ DWS CLI 升级提示、BotIdentity 上下文透传、reply-dispatcher text/markdown 降级发送
|
|
26
|
+
|
|
8
27
|
## [0.8.18] - 2026-04-21
|
|
9
28
|
|
|
10
29
|
### 修复 / Fixes
|
package/README.en.md
CHANGED
|
@@ -24,10 +24,13 @@ This plugin provides comprehensive DingTalk integration for OpenClaw:
|
|
|
24
24
|
|
|
25
25
|
| Category | Capabilities |
|
|
26
26
|
|----------|-------------|
|
|
27
|
+
| 📄 Docs | Create, append, search, and list DingTalk documents |
|
|
28
|
+
| 🔔 DING | Send urgent DING reminders to users/groups |
|
|
27
29
|
| 💬 Messaging | Receive group/DM messages, auto-reply, send text/Markdown, @mentions |
|
|
28
30
|
| ✅ Tasks | Create personal tasks, check status, set deadlines |
|
|
29
31
|
| 📊 AI Sheets | Create sheets, read/write rows, conditional queries |
|
|
30
32
|
| 📅 Calendar | Calendar management, event management (create/query/modify/delete/search), attendee management, free/busy queries |
|
|
33
|
+
| 📝 Reports | Submit daily/weekly reports, query history |
|
|
31
34
|
|
|
32
35
|
Additionally, the plugin supports:
|
|
33
36
|
|
|
@@ -42,11 +45,8 @@ Additionally, the plugin supports:
|
|
|
42
45
|
|
|
43
46
|
| Category | Capabilities |
|
|
44
47
|
|----------|-------------|
|
|
45
|
-
| 🔔 DING | Send urgent DING reminders to users/groups |
|
|
46
48
|
| ✅ Tasks | Create group tasks, check status, set deadlines |
|
|
47
|
-
| 📝 Reports | Submit daily/weekly reports, query history |
|
|
48
49
|
| 📁 Drive | Upload/download files to DingTalk Drive |
|
|
49
|
-
| 📄 Docs | Create, append, search, and list DingTalk documents |
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
package/README.md
CHANGED
|
@@ -24,10 +24,13 @@
|
|
|
24
24
|
|
|
25
25
|
| 类别 | 能力 |
|
|
26
26
|
|------|------|
|
|
27
|
+
| 📄 钉钉文档 | 创建、追加、搜索、列举钉钉文档 |
|
|
28
|
+
| 🔔 DING 消息 | 向用户/群发送强提醒 DING |
|
|
27
29
|
| 💬 消息收发 | 接收群聊/私聊消息,自动回复,发送文本/Markdown,@成员 |
|
|
28
30
|
| ✅ 待办任务 | 创建个人待办,查状态,设截止时间 |
|
|
29
31
|
| 📊 AI 表格 | 创建表格,读写行数据,条件查询 |
|
|
30
32
|
| 📅 日历日程 | 日历管理、日程管理(创建/查询/修改/删除/搜索)、参会人管理、忙闲查询 |
|
|
33
|
+
| 📝 日志 | 提交日报/周报,查历史日志 |
|
|
31
34
|
|
|
32
35
|
此外,插件还支持:
|
|
33
36
|
|
|
@@ -43,10 +46,7 @@
|
|
|
43
46
|
| 类别 | 能力 |
|
|
44
47
|
|------|------|
|
|
45
48
|
| ✅ 待办任务 | 创建群待办,查状态,设截止时间 |
|
|
46
|
-
| 🔔 DING 消息 | 向用户/群发送强提醒 DING |
|
|
47
|
-
| 📝 日志 | 提交日报/周报,查历史日志 |
|
|
48
49
|
| 📁 文件云盘 | 上传/下载文件到钉钉云盘 |
|
|
49
|
-
| 📄 钉钉文档 | 创建、追加、搜索、列举钉钉文档 |
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
@@ -99,7 +99,7 @@ openclaw gateway restart
|
|
|
99
99
|
|
|
100
100
|
- [手动配置指南](docs/DINGTALK_MANUAL_SETUP.md) — 手动填写凭证配置
|
|
101
101
|
- [钉钉 DEAP Agent 集成](docs/DEAP_AGENT_GUIDE.md) — 本地设备操作能力
|
|
102
|
-
- [多 Agent 路由配置](
|
|
102
|
+
- [多 Agent 路由配置](docs/MULTI_AGENT_SETUP.md) — 多机器人绑定不同 Agent(从零配置、完整示例、协作模式)
|
|
103
103
|
- [常见问题](docs/TROUBLESHOOTING.md) — 安装与使用问题排查
|
|
104
104
|
|
|
105
105
|
---
|
|
@@ -171,29 +171,25 @@ function clearStaging() {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
|
-
* Check if existing config
|
|
175
|
-
*
|
|
174
|
+
* Check if existing config looks like a multi-Agent setup.
|
|
175
|
+
* Returns true when EITHER condition is met:
|
|
176
|
+
* 1. channels.dingtalk-connector.accounts exists (multi-account structure)
|
|
177
|
+
* 2. bindings[] contains dingtalk-connector routing entries
|
|
178
|
+
* In these scenarios, overwriting would break the existing routing / account setup.
|
|
176
179
|
*/
|
|
177
180
|
function hasExistingMultiAgentConfig(cfg) {
|
|
181
|
+
// Condition 1: channels has an accounts sub-object (multi-account structure)
|
|
178
182
|
const dingtalkCfg = cfg?.channels?.[CHANNEL_ID];
|
|
179
|
-
|
|
183
|
+
const hasAccounts = dingtalkCfg?.accounts && typeof dingtalkCfg.accounts === 'object'
|
|
184
|
+
&& Object.keys(dingtalkCfg.accounts).length > 0;
|
|
180
185
|
|
|
181
|
-
//
|
|
182
|
-
const hasChannelCreds = Boolean(dingtalkCfg.clientId && dingtalkCfg.clientSecret);
|
|
183
|
-
// Also check accounts sub-keys for multi-account scenario
|
|
184
|
-
const hasAccountCreds = dingtalkCfg.accounts && Object.values(dingtalkCfg.accounts).some(
|
|
185
|
-
(acc) => acc && acc.clientId && acc.clientSecret
|
|
186
|
-
);
|
|
187
|
-
const hasCreds = hasChannelCreds || hasAccountCreds;
|
|
188
|
-
if (!hasCreds) return false;
|
|
189
|
-
|
|
190
|
-
// Check if bindings reference dingtalk-connector
|
|
186
|
+
// Condition 2: bindings reference dingtalk-connector
|
|
191
187
|
const bindings = Array.isArray(cfg.bindings) ? cfg.bindings : [];
|
|
192
188
|
const hasDingtalkBindings = bindings.some(
|
|
193
189
|
(b) => !b?.match?.channel || String(b.match.channel) === CHANNEL_ID
|
|
194
190
|
);
|
|
195
191
|
|
|
196
|
-
return hasDingtalkBindings;
|
|
192
|
+
return hasAccounts || hasDingtalkBindings;
|
|
197
193
|
}
|
|
198
194
|
|
|
199
195
|
function saveCredentials(clientId, clientSecret, { isLocal = false, pluginInstalled = true } = {}) {
|
|
@@ -210,9 +206,10 @@ function saveCredentials(clientId, clientSecret, { isLocal = false, pluginInstal
|
|
|
210
206
|
// If existing config already has dingtalk channels+credentials AND bindings,
|
|
211
207
|
// overwriting could break multi-Agent routing. Show credentials and let user decide.
|
|
212
208
|
if (hasExistingMultiAgentConfig(cfg)) {
|
|
213
|
-
console.log('\n' + bold('⚠
|
|
214
|
-
console.log(
|
|
215
|
-
console.log(cyan('
|
|
209
|
+
console.log('\n' + bold('⚠ Multi-Agent config detected — auto-write skipped (检测到多 Agent 配置,已跳过自动写入)'));
|
|
210
|
+
console.log(dim(' Existing channels & bindings preserved to avoid breaking routing. (已保留现有路由配置)'));
|
|
211
|
+
console.log(cyan(' You can manually edit (可手动编辑): ') + dim(getConfigPath()) + '\n');
|
|
212
|
+
console.log(cyan(' Bot credentials for this session (本次机器人凭据):'));
|
|
216
213
|
console.log(` Client ID: ${clientId}`);
|
|
217
214
|
console.log(` Client Secret: ${clientSecret}` + '\n');
|
|
218
215
|
return { skippedMultiAgent: true };
|
|
@@ -289,8 +286,9 @@ function installPlugin() {
|
|
|
289
286
|
const cfg = readConfig();
|
|
290
287
|
// Backup config before cleaning so we can restore on install failure
|
|
291
288
|
const cfgBackup = JSON.parse(JSON.stringify(cfg));
|
|
289
|
+
const isMultiAgent = hasExistingMultiAgentConfig(cfg);
|
|
292
290
|
let cfgDirty = false;
|
|
293
|
-
if (cfg.channels?.[CHANNEL_ID]) {
|
|
291
|
+
if (cfg.channels?.[CHANNEL_ID] && !isMultiAgent) {
|
|
294
292
|
delete cfg.channels[CHANNEL_ID];
|
|
295
293
|
cfgDirty = true;
|
|
296
294
|
}
|
|
@@ -330,16 +328,35 @@ function installPlugin() {
|
|
|
330
328
|
}
|
|
331
329
|
try {
|
|
332
330
|
execFileSync('openclaw', ['plugins', 'install', spec], { stdio: 'inherit' });
|
|
331
|
+
// Always restore channels & plugins.entries from pre-install backup.
|
|
332
|
+
// Both our cleaning logic AND `openclaw plugins install` can strip or simplify
|
|
333
|
+
// these entries (e.g. dropping accounts sub-object). Backup takes precedence.
|
|
334
|
+
const latestCfg = readConfig();
|
|
335
|
+
let restored = false;
|
|
336
|
+
if (cfgBackup.channels?.[CHANNEL_ID]) {
|
|
337
|
+
if (!latestCfg.channels) latestCfg.channels = {};
|
|
338
|
+
latestCfg.channels[CHANNEL_ID] = cfgBackup.channels[CHANNEL_ID];
|
|
339
|
+
restored = true;
|
|
340
|
+
}
|
|
341
|
+
if (cfgBackup.plugins?.entries?.[CHANNEL_ID]) {
|
|
342
|
+
if (!latestCfg.plugins) latestCfg.plugins = {};
|
|
343
|
+
if (!latestCfg.plugins.entries) latestCfg.plugins.entries = {};
|
|
344
|
+
latestCfg.plugins.entries[CHANNEL_ID] = cfgBackup.plugins.entries[CHANNEL_ID];
|
|
345
|
+
restored = true;
|
|
346
|
+
}
|
|
347
|
+
if (restored) {
|
|
348
|
+
writeConfig(latestCfg);
|
|
349
|
+
console.log(dim(' Restored channel config entries after install.'));
|
|
350
|
+
}
|
|
333
351
|
return true;
|
|
334
352
|
} catch (err) {
|
|
335
353
|
const errMsg = String(err.stderr || err.stdout || err.message || '');
|
|
336
354
|
const is429 = errMsg.includes('429') || errMsg.includes('Rate limit') || errMsg.includes('rate limit');
|
|
337
355
|
if (is429 && attempt < MAX_RETRIES - 1) continue;
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
356
|
+
// Always restore full backup — both our cleaning AND `openclaw plugins install`
|
|
357
|
+
// may have modified the config before the failure occurred.
|
|
358
|
+
console.log(dim(' Restoring config entries after install failure...'));
|
|
359
|
+
writeConfig(cfgBackup);
|
|
343
360
|
console.error('\n' + red('⚠ Plugin install failed.') + ' Continuing with QR authorization...\n');
|
|
344
361
|
console.error(dim(' You can install the plugin manually later:'));
|
|
345
362
|
console.error(cyan(' openclaw plugins install ' + spec) + '\n');
|
|
@@ -381,7 +398,7 @@ function getDwsSpawnEnv() {
|
|
|
381
398
|
|
|
382
399
|
// ── dws CLI install ─────────────────────────────────────────────
|
|
383
400
|
const DWS_INSTALL_SCRIPT_URL = 'https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh';
|
|
384
|
-
const DWS_NPM_PACKAGE = 'dingtalk-workspace-cli@1.0.
|
|
401
|
+
const DWS_NPM_PACKAGE = 'dingtalk-workspace-cli@1.0.13';
|
|
385
402
|
|
|
386
403
|
function isDwsInstalled() {
|
|
387
404
|
const mod = ['child', 'process'].join('_');
|
|
@@ -394,6 +411,37 @@ function isDwsInstalled() {
|
|
|
394
411
|
}
|
|
395
412
|
}
|
|
396
413
|
|
|
414
|
+
function getInstalledDwsVersion() {
|
|
415
|
+
const mod = ['child', 'process'].join('_');
|
|
416
|
+
const { execFileSync } = createRequire(import.meta.url)(`node:${mod}`);
|
|
417
|
+
try {
|
|
418
|
+
const output = execFileSync('dws', ['--version'], { stdio: 'pipe', encoding: 'utf-8' });
|
|
419
|
+
const versionMatch = output.trim().match(/(\d+\.\d+\.\d+)/);
|
|
420
|
+
return versionMatch ? versionMatch[1] : null;
|
|
421
|
+
} catch {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function getTargetDwsVersion() {
|
|
427
|
+
const versionMatch = DWS_NPM_PACKAGE.match(/@(\d+\.\d+\.\d+)$/);
|
|
428
|
+
return versionMatch ? versionMatch[1] : null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function askUserConfirmation(question) {
|
|
432
|
+
const { createInterface } = createRequire(import.meta.url)('node:readline');
|
|
433
|
+
const rl = createInterface({
|
|
434
|
+
input: globalThis['proc' + 'ess'].stdin,
|
|
435
|
+
output: globalThis['proc' + 'ess'].stdout,
|
|
436
|
+
});
|
|
437
|
+
return new Promise((resolve) => {
|
|
438
|
+
rl.question(question, (answer) => {
|
|
439
|
+
rl.close();
|
|
440
|
+
resolve(answer.trim().toLowerCase());
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
397
445
|
function installDwsCli() {
|
|
398
446
|
const mod = ['child', 'process'].join('_');
|
|
399
447
|
const { execFileSync, execSync } = createRequire(import.meta.url)(`node:${mod}`);
|
|
@@ -449,9 +497,34 @@ function isDwsAuthenticated() {
|
|
|
449
497
|
}
|
|
450
498
|
}
|
|
451
499
|
|
|
452
|
-
function ensureDwsCli() {
|
|
500
|
+
async function ensureDwsCli() {
|
|
453
501
|
if (isDwsInstalled()) {
|
|
454
|
-
|
|
502
|
+
const installedVersion = getInstalledDwsVersion();
|
|
503
|
+
const targetVersion = getTargetDwsVersion();
|
|
504
|
+
const versionDisplay = installedVersion ? `v${installedVersion}` : 'unknown version';
|
|
505
|
+
|
|
506
|
+
console.log(dim(` ✔ dws CLI already installed (${versionDisplay})`) + '\n');
|
|
507
|
+
|
|
508
|
+
// Check if a newer version is available
|
|
509
|
+
if (installedVersion && targetVersion && installedVersion !== targetVersion) {
|
|
510
|
+
console.log(orange(` ℹ A newer version of dws CLI is available: v${targetVersion} (current: v${installedVersion})`) + '\n');
|
|
511
|
+
const answer = await askUserConfirmation(
|
|
512
|
+
` Do you want to upgrade dws CLI to v${targetVersion}? (覆盖安装新版本?) [y/N] `
|
|
513
|
+
);
|
|
514
|
+
if (answer === 'y' || answer === 'yes') {
|
|
515
|
+
console.log('');
|
|
516
|
+
const upgraded = installDwsCli();
|
|
517
|
+
if (upgraded) {
|
|
518
|
+
const newVersion = getInstalledDwsVersion();
|
|
519
|
+
console.log(green(` ✔ dws CLI upgraded to v${newVersion || targetVersion}`) + '\n');
|
|
520
|
+
} else {
|
|
521
|
+
console.log(red(' ⚠ Upgrade failed. Continuing with current version.') + '\n');
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
console.log('\n' + dim(` Keeping current dws CLI v${installedVersion}`) + '\n');
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
455
528
|
if (isDwsAuthenticated()) {
|
|
456
529
|
console.log(dim(' ✔ dws CLI authenticated') + '\n');
|
|
457
530
|
} else {
|
|
@@ -514,7 +587,7 @@ Options:
|
|
|
514
587
|
|
|
515
588
|
// Step 2: Install dws CLI (unless --skip-dws)
|
|
516
589
|
if (!skipDws) {
|
|
517
|
-
ensureDwsCli();
|
|
590
|
+
await ensureDwsCli();
|
|
518
591
|
} else {
|
|
519
592
|
console.log('\n' + dim('🔧 --skip-dws: skipping dws CLI installation') + '\n');
|
|
520
593
|
}
|
|
@@ -548,7 +621,8 @@ Options:
|
|
|
548
621
|
|
|
549
622
|
if (saveResult?.skippedMultiAgent) {
|
|
550
623
|
// Multi-Agent scenario: config was NOT written, show edit-then-restart guidance
|
|
551
|
-
console.log(cyan('
|
|
624
|
+
console.log(cyan('Edit config & restart to apply (编辑配置后重启生效):') + '\n');
|
|
625
|
+
console.log(dim(' ' + getConfigPath()) + '\n');
|
|
552
626
|
console.log(cyan(' openclaw gateway restart') + '\n');
|
|
553
627
|
} else {
|
|
554
628
|
console.log(green('✔ Success! Bot configured. (机器人配置成功!)'));
|
|
@@ -329,6 +329,7 @@ async function monitorSingleAccount(opts) {
|
|
|
329
329
|
logger.info(`消息 ID: ${data.msgId || "N/A"}`);
|
|
330
330
|
logger.info(`SessionWebhook: ${data.sessionWebhook ? "已提供" : "未提供"}`);
|
|
331
331
|
logger.info(`RobotCode: ${data.robotCode || account.config?.clientId || "N/A"}`);
|
|
332
|
+
if (data.chatbotUserId || data.chatbotCorpId) console.log(`[DingTalk:${accountId}] [BotIdentity] accountId=${accountId} chatbotUserId=${data.chatbotUserId || "N/A"} chatbotCorpId=${data.chatbotCorpId || "N/A"}`);
|
|
332
333
|
data.msgId;
|
|
333
334
|
let contentPreview = "N/A";
|
|
334
335
|
if (data.text?.content) contentPreview = data.text.content.length > 100 ? data.text.content.substring(0, 100) + "..." : data.text.content;
|
package/dist/entry-bundled.mjs
CHANGED
|
@@ -23,7 +23,7 @@ var entry_bundled_default = defineBundledChannelEntry({
|
|
|
23
23
|
exportName: "setDingtalkRuntime"
|
|
24
24
|
},
|
|
25
25
|
async registerFull(api) {
|
|
26
|
-
const { registerGatewayMethods } = await import("./gateway-methods-
|
|
26
|
+
const { registerGatewayMethods } = await import("./gateway-methods-DtdiDpYK.mjs");
|
|
27
27
|
registerGatewayMethods(api);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-CF4oK_HZ.mjs";
|
|
2
2
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
3
3
|
import { r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { c as prepareMultiBotMentions, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-CyIJY4h2.mjs";
|
|
5
5
|
import { c as getUnionId, d as recallEmotionReply } from "./utils-legacy-CALCPP1t.mjs";
|
|
6
6
|
//#region src/docs.ts
|
|
7
7
|
var DingtalkDocsClient = class {
|
|
@@ -233,7 +233,7 @@ function registerGatewayMethods(api) {
|
|
|
233
233
|
const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
|
|
234
234
|
const cfg = loadConfig();
|
|
235
235
|
try {
|
|
236
|
-
const { userId, userIds, content, msgType, title, useAICard, fallbackToNormal, accountId } = params || {};
|
|
236
|
+
const { userId, userIds, content, msgType, title, useAICard, fallbackToNormal, accountId, atDingtalkIds, atUserIds, atAccountIds, atAll } = params || {};
|
|
237
237
|
warnIfAccountIdMissing(cfg, accountId, "sendToUser", log);
|
|
238
238
|
const account = resolveDingtalkAccount({
|
|
239
239
|
cfg,
|
|
@@ -244,16 +244,28 @@ function registerGatewayMethods(api) {
|
|
|
244
244
|
if (targetUserIds.length === 0) return respond(false, { error: "userId or userIds is required" });
|
|
245
245
|
if (!content) return respond(false, { error: "content is required" });
|
|
246
246
|
const target = targetUserIds.length === 1 ? { userId: targetUserIds[0] } : { userIds: targetUserIds };
|
|
247
|
-
const
|
|
247
|
+
const prepared = prepareMultiBotMentions({
|
|
248
|
+
cfg,
|
|
249
|
+
content: String(content),
|
|
250
|
+
atAccountIds,
|
|
251
|
+
atDingtalkIds
|
|
252
|
+
});
|
|
253
|
+
if (prepared.missingAccountIds.length > 0) log?.warn?.(`[Gateway][sendToUser] atAccountIds 未配置 chatbotUserId,已跳过: ${prepared.missingAccountIds.join(", ")}`);
|
|
254
|
+
const result = await sendProactive(account.config, target, prepared.content, {
|
|
248
255
|
msgType,
|
|
249
256
|
title,
|
|
250
257
|
log,
|
|
251
258
|
useAICard: useAICard !== false,
|
|
252
|
-
fallbackToNormal: fallbackToNormal !== false
|
|
259
|
+
fallbackToNormal: fallbackToNormal !== false,
|
|
260
|
+
atDingtalkIds: prepared.atDingtalkIds,
|
|
261
|
+
atUserIds,
|
|
262
|
+
atAll
|
|
253
263
|
});
|
|
254
264
|
respond(result.ok, {
|
|
255
265
|
...result,
|
|
256
|
-
usedAccountId: account.accountId
|
|
266
|
+
usedAccountId: account.accountId,
|
|
267
|
+
resolvedAtDingtalkIds: prepared.atDingtalkIds,
|
|
268
|
+
missingAtAccountIds: prepared.missingAccountIds
|
|
257
269
|
});
|
|
258
270
|
} catch (err) {
|
|
259
271
|
log?.error?.(`[Gateway][sendToUser] 错误: ${err.message}`);
|
|
@@ -276,7 +288,7 @@ function registerGatewayMethods(api) {
|
|
|
276
288
|
const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
|
|
277
289
|
const cfg = loadConfig();
|
|
278
290
|
try {
|
|
279
|
-
const { openConversationId, content, msgType, title, useAICard, fallbackToNormal, accountId } = params || {};
|
|
291
|
+
const { openConversationId, content, msgType, title, useAICard, fallbackToNormal, accountId, atDingtalkIds, atUserIds, atAccountIds, atAll } = params || {};
|
|
280
292
|
warnIfAccountIdMissing(cfg, accountId, "sendToGroup", log);
|
|
281
293
|
const account = resolveDingtalkAccount({
|
|
282
294
|
cfg,
|
|
@@ -285,16 +297,28 @@ function registerGatewayMethods(api) {
|
|
|
285
297
|
if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
|
|
286
298
|
if (!openConversationId) return respond(false, { error: "openConversationId is required" });
|
|
287
299
|
if (!content) return respond(false, { error: "content is required" });
|
|
288
|
-
const
|
|
300
|
+
const prepared = prepareMultiBotMentions({
|
|
301
|
+
cfg,
|
|
302
|
+
content: String(content),
|
|
303
|
+
atAccountIds,
|
|
304
|
+
atDingtalkIds
|
|
305
|
+
});
|
|
306
|
+
if (prepared.missingAccountIds.length > 0) log?.warn?.(`[Gateway][sendToGroup] atAccountIds 未配置 chatbotUserId,已跳过: ${prepared.missingAccountIds.join(", ")}。请让该 bot 先收一条消息,抓 [BotIdentity] 日志后回填 accounts.<id>.chatbotUserId`);
|
|
307
|
+
const result = await sendProactive(account.config, { openConversationId }, prepared.content, {
|
|
289
308
|
msgType,
|
|
290
309
|
title,
|
|
291
310
|
log,
|
|
292
311
|
useAICard: useAICard !== false,
|
|
293
|
-
fallbackToNormal: fallbackToNormal !== false
|
|
312
|
+
fallbackToNormal: fallbackToNormal !== false,
|
|
313
|
+
atDingtalkIds: prepared.atDingtalkIds,
|
|
314
|
+
atUserIds,
|
|
315
|
+
atAll
|
|
294
316
|
});
|
|
295
317
|
respond(result.ok, {
|
|
296
318
|
...result,
|
|
297
|
-
usedAccountId: account.accountId
|
|
319
|
+
usedAccountId: account.accountId,
|
|
320
|
+
resolvedAtDingtalkIds: prepared.atDingtalkIds,
|
|
321
|
+
missingAtAccountIds: prepared.missingAccountIds
|
|
298
322
|
});
|
|
299
323
|
} catch (err) {
|
|
300
324
|
log?.error?.(`[Gateway][sendToGroup] 错误: ${err.message}`);
|
|
@@ -306,7 +330,7 @@ function registerGatewayMethods(api) {
|
|
|
306
330
|
const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
|
|
307
331
|
const cfg = loadConfig();
|
|
308
332
|
try {
|
|
309
|
-
const { target, content, message, msgType, title, useAICard, fallbackToNormal, accountId } = params || {};
|
|
333
|
+
const { target, content, message, msgType, title, useAICard, fallbackToNormal, accountId, atDingtalkIds, atUserIds, atAccountIds, atAll } = params || {};
|
|
310
334
|
const actualContent = content || message;
|
|
311
335
|
warnIfAccountIdMissing(cfg, accountId, "send", log);
|
|
312
336
|
const account = resolveDingtalkAccount({
|
|
@@ -322,14 +346,29 @@ function registerGatewayMethods(api) {
|
|
|
322
346
|
if (targetStr.startsWith("user:")) sendTarget = { userId: targetStr.slice(5) };
|
|
323
347
|
else if (targetStr.startsWith("group:")) sendTarget = { openConversationId: targetStr.slice(6) };
|
|
324
348
|
else sendTarget = { userId: targetStr };
|
|
325
|
-
const
|
|
349
|
+
const prepared = prepareMultiBotMentions({
|
|
350
|
+
cfg,
|
|
351
|
+
content: String(actualContent),
|
|
352
|
+
atAccountIds,
|
|
353
|
+
atDingtalkIds
|
|
354
|
+
});
|
|
355
|
+
if (prepared.missingAccountIds.length > 0) log?.warn?.(`[Gateway][send] atAccountIds 未配置 chatbotUserId,已跳过: ${prepared.missingAccountIds.join(", ")}`);
|
|
356
|
+
const result = await sendProactive(account.config, sendTarget, prepared.content, {
|
|
326
357
|
msgType,
|
|
327
358
|
title,
|
|
328
359
|
log,
|
|
329
360
|
useAICard: useAICard !== false,
|
|
330
|
-
fallbackToNormal: fallbackToNormal !== false
|
|
361
|
+
fallbackToNormal: fallbackToNormal !== false,
|
|
362
|
+
atDingtalkIds: prepared.atDingtalkIds,
|
|
363
|
+
atUserIds,
|
|
364
|
+
atAll
|
|
365
|
+
});
|
|
366
|
+
respond(result.ok, {
|
|
367
|
+
...result,
|
|
368
|
+
usedAccountId: account.accountId,
|
|
369
|
+
resolvedAtDingtalkIds: prepared.atDingtalkIds,
|
|
370
|
+
missingAtAccountIds: prepared.missingAccountIds
|
|
331
371
|
});
|
|
332
|
-
respond(result.ok, result);
|
|
333
372
|
} catch (err) {
|
|
334
373
|
log?.error?.(`[Gateway][send] 错误: ${err.message}`);
|
|
335
374
|
respond(false, { error: err.message });
|
|
@@ -595,6 +634,118 @@ function registerGatewayMethods(api) {
|
|
|
595
634
|
respond(false, { error: err.message });
|
|
596
635
|
}
|
|
597
636
|
});
|
|
637
|
+
/**
|
|
638
|
+
* 列出所有已配置的钉钉机器人账号(含元数据),供多 Agent 协作时查询"队友机器人"使用。
|
|
639
|
+
*
|
|
640
|
+
* 返回字段:
|
|
641
|
+
* - accountId: 在 openclaw.json 里 accounts 配置的 key(用于 sendToGroup 的 accountId 参数)
|
|
642
|
+
* - name: 友好显示名(accounts.<id>.name)
|
|
643
|
+
* - chatbotUserId: 该机器人加密 ID(如配置在 accounts.<id>.chatbotUserId 里),可用于 atDingtalkIds
|
|
644
|
+
* - clientId: AppKey(脱敏前 8 位)
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* ```typescript
|
|
648
|
+
* const r = await gateway.call('dingtalk-connector.listAccounts');
|
|
649
|
+
* // -> [{ accountId: 'main-bot', name: '主助手机器人', chatbotUserId: '$:LWCP_v1:xxx', ... }, ...]
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
api.registerGatewayMethod("dingtalk-connector.listAccounts", async ({ context, respond }) => {
|
|
653
|
+
const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
|
|
654
|
+
const cfg = loadConfig();
|
|
655
|
+
try {
|
|
656
|
+
const root = cfg.channels?.["dingtalk-connector"];
|
|
657
|
+
const accountsMap = root?.accounts || {};
|
|
658
|
+
const mentionTable = buildBotMentionTable(cfg);
|
|
659
|
+
const mentionByAccountId = new Map(mentionTable.map((e) => [e.accountId, e]));
|
|
660
|
+
respond(true, { accounts: Object.keys(accountsMap).map((id) => {
|
|
661
|
+
const a = accountsMap[id] || {};
|
|
662
|
+
const cid = String(a.clientId ?? root?.clientId ?? "");
|
|
663
|
+
const mention = mentionByAccountId.get(id);
|
|
664
|
+
return {
|
|
665
|
+
accountId: id,
|
|
666
|
+
name: a.name || id,
|
|
667
|
+
enabled: a.enabled !== false,
|
|
668
|
+
chatbotUserId: a.chatbotUserId || void 0,
|
|
669
|
+
chatbotCorpId: a.chatbotCorpId || void 0,
|
|
670
|
+
clientId: cid ? cid.substring(0, 8) + "..." : void 0,
|
|
671
|
+
agentIds: mention?.agentIds || [],
|
|
672
|
+
aliases: mention?.aliases || [],
|
|
673
|
+
mentionReady: !!a.chatbotUserId
|
|
674
|
+
};
|
|
675
|
+
}) });
|
|
676
|
+
} catch (err) {
|
|
677
|
+
log?.error?.(`[Gateway][listAccounts] 错误: ${err.message}`);
|
|
678
|
+
respond(false, { error: err.message });
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
/**
|
|
682
|
+
* 多 bot 协作自检:检查每个 account 是否具备在群里互 @ 的能力。
|
|
683
|
+
*
|
|
684
|
+
* 一个 bot 能被其它 bot @ 的前提:
|
|
685
|
+
* 1. `accounts.<id>.chatbotUserId` / `chatbotCorpId` 已填(从 `[BotIdentity]` 日志抓回来)
|
|
686
|
+
* 2. bot 当前 enabled 且配了 clientId / clientSecret
|
|
687
|
+
*
|
|
688
|
+
* 返回的报告可以直接贴给用户,告诉他下一步该干什么
|
|
689
|
+
* (比如"给 dev-bot 发一条消息后回填 chatbotUserId")。
|
|
690
|
+
*
|
|
691
|
+
* @example
|
|
692
|
+
* ```typescript
|
|
693
|
+
* const r = await gateway.call('dingtalk-connector.bootstrapBotIdentity');
|
|
694
|
+
* // -> {
|
|
695
|
+
* // ready: false,
|
|
696
|
+
* // totalAccounts: 2,
|
|
697
|
+
* // readyAccounts: 1,
|
|
698
|
+
* // missingChatbotUserId: ['dev-bot'],
|
|
699
|
+
* // report: '...'
|
|
700
|
+
* // }
|
|
701
|
+
* ```
|
|
702
|
+
*/
|
|
703
|
+
api.registerGatewayMethod("dingtalk-connector.bootstrapBotIdentity", async ({ context, respond }) => {
|
|
704
|
+
const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
|
|
705
|
+
const cfg = loadConfig();
|
|
706
|
+
try {
|
|
707
|
+
const accountsMap = (cfg.channels?.["dingtalk-connector"])?.accounts || {};
|
|
708
|
+
const ids = Object.keys(accountsMap);
|
|
709
|
+
const missingChatbotUserId = [];
|
|
710
|
+
const missingCredentials = [];
|
|
711
|
+
const disabled = [];
|
|
712
|
+
const ready = [];
|
|
713
|
+
for (const id of ids) {
|
|
714
|
+
const a = accountsMap[id] || {};
|
|
715
|
+
if (a.enabled === false) {
|
|
716
|
+
disabled.push(id);
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const hasCreds = !!(a.clientId && a.clientSecret);
|
|
720
|
+
if (!hasCreds) missingCredentials.push(id);
|
|
721
|
+
if (!a.chatbotUserId) missingChatbotUserId.push(id);
|
|
722
|
+
else if (hasCreds) ready.push({
|
|
723
|
+
accountId: id,
|
|
724
|
+
name: a.name,
|
|
725
|
+
chatbotUserId: a.chatbotUserId
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
const reportLines = [];
|
|
729
|
+
reportLines.push(`[BotIdentity] 已配置 ${ids.length} 个账号,其中 ${ready.length} 个可参与多 bot 互相 @`);
|
|
730
|
+
if (ready.length > 0) reportLines.push("[OK] Ready: " + ready.map((r) => `${r.accountId}(${r.name || ""})`).join(", "));
|
|
731
|
+
if (missingChatbotUserId.length > 0) reportLines.push(`[WARN] 缺少 chatbotUserId: ${missingChatbotUserId.join(", ")} — 请让这些 bot 在钉钉里各收一条消息,然后在终端 grep "[BotIdentity]" 抓到加密 ID 后回填到 openclaw.json 对应 account`);
|
|
732
|
+
if (missingCredentials.length > 0) reportLines.push(`[ERR] 缺少 clientId/clientSecret: ${missingCredentials.join(", ")}`);
|
|
733
|
+
if (disabled.length > 0) reportLines.push(`[PAUSED] 已禁用: ${disabled.join(", ")}`);
|
|
734
|
+
respond(true, {
|
|
735
|
+
ready: missingChatbotUserId.length === 0 && missingCredentials.length === 0 && ready.length > 0,
|
|
736
|
+
totalAccounts: ids.length,
|
|
737
|
+
readyAccounts: ready.length,
|
|
738
|
+
readyList: ready,
|
|
739
|
+
missingChatbotUserId,
|
|
740
|
+
missingCredentials,
|
|
741
|
+
disabled,
|
|
742
|
+
report: reportLines.join("\n")
|
|
743
|
+
});
|
|
744
|
+
} catch (err) {
|
|
745
|
+
log?.error?.(`[Gateway][bootstrapBotIdentity] 错误: ${err.message}`);
|
|
746
|
+
respond(false, { error: err.message });
|
|
747
|
+
}
|
|
748
|
+
});
|
|
598
749
|
api.registerGatewayMethod("dingtalk-connector.probe", async ({ context, respond }) => {
|
|
599
750
|
const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
|
|
600
751
|
const cfg = loadConfig();
|
package/dist/index.d.mts
CHANGED
|
@@ -69,6 +69,11 @@ declare const DingtalkConfigSchema: z$1.ZodObject<{
|
|
|
69
69
|
debug: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
70
70
|
enableMediaUpload: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
71
71
|
systemPrompt: z$1.ZodOptional<z$1.ZodString>;
|
|
72
|
+
groupReplyMode: z$1.ZodOptional<z$1.ZodEnum<{
|
|
73
|
+
aicard: "aicard";
|
|
74
|
+
text: "text";
|
|
75
|
+
markdown: "markdown";
|
|
76
|
+
}>>;
|
|
72
77
|
enabled: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
73
78
|
name: z$1.ZodOptional<z$1.ZodString>;
|
|
74
79
|
clientId: z$1.ZodOptional<z$1.ZodUnion<readonly [z$1.ZodString, z$1.ZodNumber]>>;
|
|
@@ -81,6 +86,8 @@ declare const DingtalkConfigSchema: z$1.ZodObject<{
|
|
|
81
86
|
provider: z$1.ZodString;
|
|
82
87
|
id: z$1.ZodString;
|
|
83
88
|
}, z$1.core.$strip>]>>;
|
|
89
|
+
chatbotUserId: z$1.ZodOptional<z$1.ZodString>;
|
|
90
|
+
chatbotCorpId: z$1.ZodOptional<z$1.ZodString>;
|
|
84
91
|
}, z$1.core.$strict>>>>;
|
|
85
92
|
allowFrom: z$1.ZodOptional<z$1.ZodArray<z$1.ZodUnion<readonly [z$1.ZodString, z$1.ZodNumber]>>>;
|
|
86
93
|
groupAllowFrom: z$1.ZodOptional<z$1.ZodArray<z$1.ZodUnion<readonly [z$1.ZodString, z$1.ZodNumber]>>>;
|
|
@@ -113,6 +120,11 @@ declare const DingtalkConfigSchema: z$1.ZodObject<{
|
|
|
113
120
|
debug: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
114
121
|
enableMediaUpload: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
115
122
|
systemPrompt: z$1.ZodOptional<z$1.ZodString>;
|
|
123
|
+
groupReplyMode: z$1.ZodOptional<z$1.ZodEnum<{
|
|
124
|
+
aicard: "aicard";
|
|
125
|
+
text: "text";
|
|
126
|
+
markdown: "markdown";
|
|
127
|
+
}>>;
|
|
116
128
|
enabled: z$1.ZodOptional<z$1.ZodBoolean>;
|
|
117
129
|
defaultAccount: z$1.ZodOptional<z$1.ZodString>;
|
|
118
130
|
clientId: z$1.ZodOptional<z$1.ZodUnion<readonly [z$1.ZodString, z$1.ZodNumber]>>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
|
-
import { i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-
|
|
2
|
-
import { t as registerGatewayMethods } from "./gateway-methods-
|
|
1
|
+
import { i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-D35JIkCZ.mjs";
|
|
2
|
+
import { t as registerGatewayMethods } from "./gateway-methods-DI8lkjSd.mjs";
|
|
3
3
|
//#region index.ts
|
|
4
|
+
/**
|
|
5
|
+
* 检测同一 plugin id 在多个路径被加载的情况。
|
|
6
|
+
*
|
|
7
|
+
* 典型场景:`openclaw.json` 里配了本地 `plugins.load.paths`(开发源码),同时 `~/.openclaw/extensions/dingtalk-connector`
|
|
8
|
+
* 也装了 npm 全局扩展,两份 dist/index.mjs 都被 gateway 加载 → 两份 stream 订阅互相抢占,
|
|
9
|
+
* 表现就是"消息时而能收到,时而收不到 / 回复丢失"。
|
|
10
|
+
*
|
|
11
|
+
* 这里做一个轻量自检:
|
|
12
|
+
* - 把当前 index.mjs 的绝对路径写入全局 Symbol 表
|
|
13
|
+
* - 同名 plugin id 被第二次注册时打印警告(或在 `DINGTALK_STRICT_DUPLICATE_LOAD=1` 时直接抛错)
|
|
14
|
+
*
|
|
15
|
+
* 作为运行时兜底,建议同时在 `openclaw.json` 只保留一条加载路径。
|
|
16
|
+
*/
|
|
17
|
+
const DUPLICATE_LOAD_SYMBOL = Symbol.for("@dingtalk-connector/loaded-paths");
|
|
18
|
+
function recordAndCheckLoadPath(api) {
|
|
19
|
+
try {
|
|
20
|
+
const g = globalThis;
|
|
21
|
+
const store = g[DUPLICATE_LOAD_SYMBOL] ?? /* @__PURE__ */ new Map();
|
|
22
|
+
g[DUPLICATE_LOAD_SYMBOL] = store;
|
|
23
|
+
const pluginId = "dingtalk-connector";
|
|
24
|
+
const here = typeof import.meta !== "undefined" && import.meta?.url ? String(import.meta.url) : "<unknown>";
|
|
25
|
+
const paths = store.get(pluginId) ?? /* @__PURE__ */ new Set();
|
|
26
|
+
paths.add(here);
|
|
27
|
+
store.set(pluginId, paths);
|
|
28
|
+
if (paths.size > 1) {
|
|
29
|
+
const msg = `[dingtalk-connector] 检测到同 plugin id 被多个路径加载:\n - ${Array.from(paths).join("\n - ")}\n这会导致 stream 回调互相抢占、消息丢失。请在 openclaw.json 里只保留一条加载方式:\n • 本地开发:保留 plugins.load.paths,删除 ~/.openclaw/extensions/dingtalk-connector\n • 生产:只保留 extensions 安装目录,删除 plugins.load.paths 里对本地仓库的引用`;
|
|
30
|
+
if (process.env.DINGTALK_STRICT_DUPLICATE_LOAD === "1") throw new Error(msg);
|
|
31
|
+
api.logger?.warn?.(msg);
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (process.env.DINGTALK_STRICT_DUPLICATE_LOAD === "1") throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
4
37
|
function register(api) {
|
|
38
|
+
recordAndCheckLoadPath(api);
|
|
5
39
|
setDingtalkRuntime(api.runtime);
|
|
6
40
|
api.registerChannel({ plugin: dingtalkPlugin });
|
|
7
41
|
registerGatewayMethods(api);
|