@dingtalk-real-ai/dingtalk-connector 0.8.18-beta.0 → 0.8.18

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 CHANGED
@@ -5,6 +5,22 @@ 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.18] - 2026-04-21
9
+
10
+ ### 修复 / Fixes
11
+ - **AI Card 流式中断残留修复 (#463)** — Gateway 重启后 AI Card 卡在思考中动画、表情标签未撤回;新增 `fixStuckCards` Gateway Method 支持手动修复卡住的 AI Card 和撤回残留表情
12
+ **AI Card stuck state fix (#463)** — After gateway restart, AI Card stuck in thinking animation and emotion tag not recalled; added `fixStuckCards` Gateway Method for manual recovery
13
+
14
+ - **多 Agent 配置覆盖保护** — 安装向导检测到已有钉钉 channels + bindings 配置时,跳过自动写入,展示凭证信息让用户自行决定,避免多 Agent 路由配置被意外覆盖
15
+ **Multi-Agent config overwrite protection** — Install wizard detects existing DingTalk channels + bindings config, skips auto-write and shows credentials for user to decide
16
+
17
+ ### 改进 / Improvements
18
+ - **OpenClaw 版本兼容性** — peerDependency 从 >=2026.3.23 升级到 >=2026.4.9,兼容 OpenClaw 2026.4.15 中的 plugin-sdk 变更
19
+ **OpenClaw version compatibility** — peerDependency bumped from >=2026.3.23 to >=2026.4.9
20
+
21
+ - **README 能力展示优化** — 已支持的业务能力(待办、AI 表格、日历日程)整合到主能力表,Coming Soon 区域更新
22
+ **README capability display** — Supported capabilities consolidated into main table; Coming Soon section updated
23
+
8
24
  ## [0.8.17] - 2026-04-16
9
25
 
10
26
  ### 新增 / Added
@@ -15,6 +15,7 @@ import { homedir } from 'node:os';
15
15
  const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
16
16
  const green = (s) => `\x1b[32m${s}\x1b[0m`;
17
17
  const red = (s) => `\x1b[31m${s}\x1b[0m`;
18
+ const orange = (s) => `\x1b[38;5;208m${s}\x1b[0m`;
18
19
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
19
20
  const bold = (s) => `\x1b[1m${s}\x1b[0m`;
20
21
 
@@ -210,13 +211,11 @@ function saveCredentials(clientId, clientSecret, { isLocal = false, pluginInstal
210
211
  // overwriting could break multi-Agent routing. Show credentials and let user decide.
211
212
  if (hasExistingMultiAgentConfig(cfg)) {
212
213
  console.log('\n' + bold('⚠ 检测到已有钉钉 channels 和 bindings 配置(多 Agent 场景)'));
213
- console.log(dim(' 直接覆盖可能影响现有的多 Agent 路由配置,已跳过自动写入。') + '\n');
214
+ console.log(orange(' 直接覆盖可能影响现有的多 Agent 路由配置,已跳过自动写入。') + '\n');
214
215
  console.log(cyan(' 本次选择/创建的机器人信息:'));
215
- console.log(green(` Client ID: ${clientId}`));
216
- console.log(green(` Client Secret: ${clientSecret}`) + '\n');
217
- console.log(dim(' 请自行决定是否修改 ~/.openclaw/openclaw.json 中的配置。'));
218
- console.log(dim(' 如需新增账号,可在 channels.dingtalk-connector.accounts 下添加。') + '\n');
219
- return;
216
+ console.log(` Client ID: ${clientId}`);
217
+ console.log(` Client Secret: ${clientSecret}` + '\n');
218
+ return { skippedMultiAgent: true };
220
219
  }
221
220
 
222
221
  // ── channels.[CHANNEL_ID] ──
@@ -542,25 +541,31 @@ Options:
542
541
  console.log('\n' + dim('Saving local configuration... (正在进行本地配置...)') + '\n');
543
542
 
544
543
  // Step 5: Save config
545
- saveCredentials(creds.clientId, creds.clientSecret, { isLocal, pluginInstalled });
544
+ const saveResult = saveCredentials(creds.clientId, creds.clientSecret, { isLocal, pluginInstalled });
546
545
 
547
546
  // Step 5.1: Inject DWS environment variables for dws CLI integration
548
547
  injectDwsEnvVars(creds.clientId, creds.clientSecret);
549
548
 
550
- console.log(green('✔ Success! Bot configured. (机器人配置成功!)'));
551
- console.log(dim(` Configuration saved to ${getConfigPath()}`) + '\n');
552
-
553
- // Step 6: Post-install guidance
554
- if (!pluginInstalled && !isLocal) {
555
- console.log(red('⚠ Plugin was not installed.') + ' Credentials saved for later.\n');
556
- console.log('Please install the plugin, then re-run to apply config (no QR needed):\n');
557
- console.log(cyan(' openclaw plugins install ' + getInstallSpec()));
558
- console.log(cyan(' npx -y ' + PKG_NAME + ' install') + '\n');
559
- } else {
560
- console.log(cyan('Please restart the gateway to apply changes:') + '\n');
549
+ if (saveResult?.skippedMultiAgent) {
550
+ // Multi-Agent scenario: config was NOT written, show edit-then-restart guidance
551
+ console.log(cyan('After editing the config, please restart the gateway to apply changes:') + '\n');
561
552
  console.log(cyan(' openclaw gateway restart') + '\n');
562
- // Note: the ~3 min warm-up is an OpenClaw gateway behaviour, not plugin-specific.
563
- console.log(green(' After restart, allow ~3 min for gateway to initialize — then chat with your bot! (网关初始化约3分钟,完成即可对话)') + '\n');
553
+ } else {
554
+ console.log(green(' Success! Bot configured. (机器人配置成功!)'));
555
+ console.log(dim(` Configuration saved to ${getConfigPath()}`) + '\n');
556
+
557
+ // Step 6: Post-install guidance
558
+ if (!pluginInstalled && !isLocal) {
559
+ console.log(red('⚠ Plugin was not installed.') + ' Credentials saved for later.\n');
560
+ console.log('Please install the plugin, then re-run to apply config (no QR needed):\n');
561
+ console.log(cyan(' openclaw plugins install ' + getInstallSpec()));
562
+ console.log(cyan(' npx -y ' + PKG_NAME + ' install') + '\n');
563
+ } else {
564
+ console.log(cyan('Please restart the gateway to apply changes:') + '\n');
565
+ console.log(cyan(' openclaw gateway restart') + '\n');
566
+ // Note: the ~3 min warm-up is an OpenClaw gateway behaviour, not plugin-specific.
567
+ console.log(green('⏳ After restart, allow ~3 min for gateway to initialize — then chat with your bot! (网关初始化约3分钟,完成即可对话)') + '\n');
568
+ }
564
569
  }
565
570
  } catch (err) {
566
571
  console.error('\n' + red('❌ Authorization failed: ') + err.message + '\n');
@@ -236,7 +236,6 @@ async function monitorSingleAccount(opts) {
236
236
  }
237
237
  try {
238
238
  client.socket?.ping();
239
- lastSocketAvailableTime = Date.now();
240
239
  logger.debug(`💓 发送 PING 心跳成功`);
241
240
  } catch (err) {
242
241
  logger.warn(`发送 PING 失败:${err.message}`);
@@ -1,3 +1,6 @@
1
+ import * as _$openclaw_plugin_sdk_channel_entry_contract0 from "openclaw/plugin-sdk/channel-entry-contract";
2
+ import * as _$openclaw_plugin_sdk0 from "openclaw/plugin-sdk";
3
+
1
4
  //#region entry-bundled.d.ts
2
5
  /**
3
6
  * Bundled entry for openclaw-fork compatibility.
@@ -8,6 +11,6 @@
8
11
  * Usage in package.json exports:
9
12
  * "./bundled" → this file
10
13
  */
11
- declare const _default: any;
14
+ declare const _default: _$openclaw_plugin_sdk_channel_entry_contract0.BundledChannelEntryContract<_$openclaw_plugin_sdk0.ChannelPlugin>;
12
15
  //#endregion
13
16
  export { _default as default };
@@ -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-Csm-xIsh.mjs");
26
+ const { registerGatewayMethods } = await import("./gateway-methods-DzjIk6X4.mjs");
27
27
  registerGatewayMethods(api);
28
28
  }
29
29
  });
@@ -1,8 +1,8 @@
1
1
  import { a as resolveDingtalkAccount } 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 { r as sendProactive } from "./messaging-CN_wQxw1.mjs";
5
- import { c as getUnionId } from "./utils-legacy-CALCPP1t.mjs";
4
+ import { o as finishAICard, r as sendProactive } from "./messaging-CN_wQxw1.mjs";
5
+ import { c as getUnionId, d as recallEmotionReply } from "./utils-legacy-CALCPP1t.mjs";
6
6
  //#region src/docs.ts
7
7
  var DingtalkDocsClient = class {
8
8
  config;
@@ -482,6 +482,100 @@ function registerGatewayMethods(api) {
482
482
  respond(false, { error: err.message });
483
483
  }
484
484
  });
485
+ /**
486
+ * 修复卡住的 AI Card 和/或残留的🤔表情标签
487
+ *
488
+ * 使用场景:Gateway 重启导致流式响应中断,AI Card 停留在"思考中"状态,
489
+ * 或用户消息上的🤔表情标签未被自动撤回。
490
+ *
491
+ * @example 修复卡住的 AI Card
492
+ * ```typescript
493
+ * await gateway.call('dingtalk-connector.fixStuckCards', {
494
+ * cardInstanceId: 'card_1713600000000_abc12345',
495
+ * content: '(回复中断,请重新提问)'
496
+ * });
497
+ * ```
498
+ *
499
+ * @example 撤回残留的🤔表情
500
+ * ```typescript
501
+ * await gateway.call('dingtalk-connector.fixStuckCards', {
502
+ * msgId: 'msgXXX',
503
+ * conversationId: 'cidXXX'
504
+ * });
505
+ * ```
506
+ *
507
+ * @example 同时修复两者
508
+ * ```typescript
509
+ * await gateway.call('dingtalk-connector.fixStuckCards', {
510
+ * cardInstanceId: 'card_1713600000000_abc12345',
511
+ * msgId: 'msgXXX',
512
+ * conversationId: 'cidXXX'
513
+ * });
514
+ * ```
515
+ */
516
+ api.registerGatewayMethod("dingtalk-connector.fixStuckCards", async ({ context, params, respond }) => {
517
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
518
+ const cfg = loadConfig();
519
+ try {
520
+ const { cardInstanceId, content, msgId, conversationId, accountId } = params || {};
521
+ const account = resolveDingtalkAccount({
522
+ cfg,
523
+ accountId
524
+ });
525
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
526
+ if (!cardInstanceId && !msgId) return respond(false, {
527
+ error: "At least one of cardInstanceId or msgId is required",
528
+ usage: {
529
+ cardInstanceId: "(optional) AI Card outTrackId, found in logs like \"outTrackId=card_...\"",
530
+ content: "(optional) Final card content, defaults to \"(回复中断,请重新提问)\"",
531
+ msgId: "(optional) Message ID for emotion recall, found in logs like \"msgId=...\"",
532
+ conversationId: "(optional) Required together with msgId for emotion recall"
533
+ }
534
+ });
535
+ const results = {};
536
+ if (cardInstanceId) try {
537
+ const { getAccessToken } = await import("./utils-legacy-CFYDBM4r.mjs");
538
+ const token = await getAccessToken(account.config);
539
+ await finishAICard({
540
+ cardInstanceId: String(cardInstanceId),
541
+ accessToken: token,
542
+ tokenExpireTime: Date.now() + 7200 * 1e3,
543
+ inputingStarted: true
544
+ }, String(content || "(回复中断,请重新提问)"), account.config, log);
545
+ results.card = { ok: true };
546
+ log?.info?.(`[Gateway][fixStuckCards] AI Card 修复成功: ${cardInstanceId}`);
547
+ } catch (err) {
548
+ results.card = {
549
+ ok: false,
550
+ error: err.message
551
+ };
552
+ log?.error?.(`[Gateway][fixStuckCards] AI Card 修复失败: ${err.message}`);
553
+ }
554
+ if (msgId && conversationId) try {
555
+ await recallEmotionReply(account.config, {
556
+ msgId,
557
+ conversationId,
558
+ robotCode: account.config.clientId
559
+ }, log);
560
+ results.emotion = { ok: true };
561
+ log?.info?.(`[Gateway][fixStuckCards] 表情撤回成功: msgId=${msgId}`);
562
+ } catch (err) {
563
+ results.emotion = {
564
+ ok: false,
565
+ error: err.message
566
+ };
567
+ log?.error?.(`[Gateway][fixStuckCards] 表情撤回失败: ${err.message}`);
568
+ }
569
+ else if (msgId && !conversationId) results.emotion = {
570
+ ok: false,
571
+ error: "conversationId is required together with msgId"
572
+ };
573
+ respond(Object.values(results).every((r) => r.ok), results);
574
+ } catch (err) {
575
+ log?.error?.(`[Gateway][fixStuckCards] 错误: ${err.message}`);
576
+ respond(false, { error: err.message });
577
+ }
578
+ });
485
579
  api.registerGatewayMethod("dingtalk-connector.probe", async ({ context, respond }) => {
486
580
  const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
487
581
  const cfg = loadConfig();
@@ -0,0 +1,2 @@
1
+ import { t as registerGatewayMethods } from "./gateway-methods-CWccImhR.mjs";
2
+ export { registerGatewayMethods };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-CnauOkGt.mjs";
2
- import { t as registerGatewayMethods } from "./gateway-methods-B3AEEDTe.mjs";
1
+ import { i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-CAQ2GHj-.mjs";
2
+ import { t as registerGatewayMethods } from "./gateway-methods-CWccImhR.mjs";
3
3
  //#region index.ts
4
4
  function register(api) {
5
5
  setDingtalkRuntime(api.runtime);
@@ -1,6 +1,6 @@
1
1
  import { u as uploadMediaToDingTalk } from "./media-DUMfXnwJ.mjs";
2
2
  import { a as resolveDingtalkAccount } from "./accounts-CF4oK_HZ.mjs";
3
- import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-CnauOkGt.mjs";
3
+ import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-CAQ2GHj-.mjs";
4
4
  import { n as createLoggerFromConfig } from "./logger-BDWwViGT.mjs";
5
5
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
6
6
  import { i as getOapiAccessToken } from "./utils-CIfI_3Jh.mjs";
@@ -890,8 +890,8 @@ async function monitorDingtalkProvider(opts = {}) {
890
890
  const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
891
891
  const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
892
892
  import("./accounts-BSIiLyZa.mjs"),
893
- import("./message-handler-C17j--kj.mjs"),
894
- import("./connection-DgBppP0q.mjs")
893
+ import("./message-handler-C843X4O6.mjs"),
894
+ import("./connection-JLte7CFe.mjs")
895
895
  ]);
896
896
  const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
897
897
  const { handleDingTalkMessage } = monitorAccountModule;
@@ -0,0 +1,64 @@
1
+ # Release Notes - v0.8.18
2
+
3
+ ## 🎉 新版本亮点 / Highlights
4
+
5
+ 本次版本聚焦 **流式中断故障恢复** 和 **多 Agent 安装保护**。新增 `fixStuckCards` Gateway Method 解决 Gateway 重启后 AI Card 卡住和表情残留问题;安装向导增加多 Agent 配置检测,防止已有路由配置被意外覆盖。
6
+
7
+ This release focuses on **streaming interruption recovery** and **multi-Agent install protection**. Added `fixStuckCards` Gateway Method to fix stuck AI Cards and lingering emotion tags after gateway restart; install wizard now detects existing multi-Agent config to prevent accidental overwrite.
8
+
9
+ ## 🐛 修复 / Fixes
10
+
11
+ - **AI Card 流式中断残留修复 (#463) / AI Card stuck state fix**
12
+ Gateway 重启导致流式响应中断时,AI Card 停留在"思考中"动画、用户消息上的🤔表情标签未自动撤回。新增 `dingtalk-connector.fixStuckCards` Gateway Method,支持手动修复卡住的 AI Card 和/或撤回残留表情。
13
+ When streaming is interrupted by gateway restart, AI Card stays in "thinking" animation and 🤔 emotion tag is not auto-recalled. Added `dingtalk-connector.fixStuckCards` Gateway Method for manual recovery.
14
+
15
+ ```typescript
16
+ // 修复卡住的 AI Card
17
+ await gateway.call('dingtalk-connector.fixStuckCards', {
18
+ cardInstanceId: 'card_1713600000000_abc12345',
19
+ content: '(回复中断,请重新提问)'
20
+ });
21
+
22
+ // 撤回残留的🤔表情
23
+ await gateway.call('dingtalk-connector.fixStuckCards', {
24
+ msgId: 'msgXXX',
25
+ conversationId: 'cidXXX'
26
+ });
27
+ ```
28
+
29
+ - **多 Agent 配置覆盖保护 / Multi-Agent config overwrite protection**
30
+ 安装向导检测到 `openclaw.json` 中已有钉钉 channels + bindings 配置(多 Agent 场景)时,**跳过自动写入**,展示本次选择的机器人凭证信息,让用户自行决定是否修改配置。避免多 Agent 路由被意外覆盖。
31
+ Install wizard detects existing DingTalk channels + bindings config (multi-Agent scenario), skips auto-write, and displays credentials for user to decide. Prevents accidental multi-Agent routing breakage.
32
+
33
+ ## ✅ 改进 / Improvements
34
+
35
+ - **OpenClaw 版本兼容性 / OpenClaw version compatibility**
36
+ `peerDependency` 从 `>=2026.3.23` 升级到 `>=2026.4.9`,兼容 OpenClaw 2026.4.15 中的 plugin-sdk 变更。
37
+ `peerDependency` bumped from `>=2026.3.23` to `>=2026.4.9`, compatible with OpenClaw 2026.4.15 plugin-sdk changes.
38
+
39
+ - **README 能力展示优化 / README capability display**
40
+ 已支持的业务能力(待办任务、AI 表格、日历日程)从独立区块整合到主能力表格,Coming Soon 区域同步更新。
41
+ Supported business capabilities (Todo, AI Table, Calendar) consolidated into main capability table; Coming Soon section updated.
42
+
43
+ ## 📥 安装升级 / Installation & Upgrade
44
+
45
+ ```bash
46
+ npx openclaw@latest add @dingtalk-real-ai/dingtalk-connector
47
+ ```
48
+
49
+ 或指定版本:
50
+ ```bash
51
+ npx openclaw@latest add @dingtalk-real-ai/dingtalk-connector@0.8.18
52
+ ```
53
+
54
+ ## 🔗 相关链接 / Related Links
55
+
56
+ - [完整变更日志 / Full Changelog](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/CHANGELOG.md)
57
+ - [使用文档 / Documentation](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/README.md)
58
+ - [故障排查 / Troubleshooting](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/docs/TROUBLESHOOTING.md)
59
+
60
+ ---
61
+
62
+ **发布日期 / Release Date**:2026-04-21
63
+ **版本号 / Version**:v0.8.18
64
+ **兼容性 / Compatibility**:OpenClaw Gateway 2026.4.9+
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dingtalk-connector",
3
3
  "name": "DingTalk Channel",
4
- "version": "0.8.18-beta.0",
4
+ "version": "0.8.18",
5
5
  "description": "Official OpenClaw DingTalk channel plugin | 钉钉官方 OpenClaw 插件",
6
6
  "author": "DingTalk Real Team",
7
7
  "main": "index.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dingtalk-real-ai/dingtalk-connector",
3
- "version": "0.8.18-beta.0",
3
+ "version": "0.8.18",
4
4
  "description": "Official OpenClaw DingTalk channel plugin | 钉钉官方 OpenClaw 插件",
5
5
  "type": "module",
6
6
  "exports": {
@@ -403,10 +403,10 @@ export async function monitorSingleAccount(
403
403
  return;
404
404
  }
405
405
 
406
- // 【发送原生 Ping】更新可用时间
406
+ // 【发送原生 Ping】仅发送,不刷新时间戳;
407
+ // 只有收到 pong 响应时才更新 lastSocketAvailableTime(见 setupPongListener)
407
408
  try {
408
409
  client.socket?.ping();
409
- lastSocketAvailableTime = Date.now();
410
410
  logger.debug(`💓 发送 PING 心跳成功`);
411
411
  } catch (err: any) {
412
412
  logger.warn(`发送 PING 失败:${err.message}`);
@@ -8,7 +8,9 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
8
8
  import { resolveDingtalkAccount } from "./config/accounts.ts";
9
9
  import { DingtalkDocsClient } from "./docs.ts";
10
10
  import { sendProactive } from "./services/messaging.ts";
11
- import { getUnionId } from "./utils/utils-legacy.ts";
11
+ import { getUnionId, recallEmotionReply } from "./utils/utils-legacy.ts";
12
+ import { finishAICard } from "./services/messaging/card.ts";
13
+ import type { AICardInstance } from "./services/messaging/card.ts";
12
14
 
13
15
  /**
14
16
  * 注册所有 Gateway Methods
@@ -380,6 +382,111 @@ export function registerGatewayMethods(api: OpenClawPluginApi) {
380
382
  }
381
383
  });
382
384
 
385
+ // ============ 故障恢复类 ============
386
+
387
+ /**
388
+ * 修复卡住的 AI Card 和/或残留的🤔表情标签
389
+ *
390
+ * 使用场景:Gateway 重启导致流式响应中断,AI Card 停留在"思考中"状态,
391
+ * 或用户消息上的🤔表情标签未被自动撤回。
392
+ *
393
+ * @example 修复卡住的 AI Card
394
+ * ```typescript
395
+ * await gateway.call('dingtalk-connector.fixStuckCards', {
396
+ * cardInstanceId: 'card_1713600000000_abc12345',
397
+ * content: '(回复中断,请重新提问)'
398
+ * });
399
+ * ```
400
+ *
401
+ * @example 撤回残留的🤔表情
402
+ * ```typescript
403
+ * await gateway.call('dingtalk-connector.fixStuckCards', {
404
+ * msgId: 'msgXXX',
405
+ * conversationId: 'cidXXX'
406
+ * });
407
+ * ```
408
+ *
409
+ * @example 同时修复两者
410
+ * ```typescript
411
+ * await gateway.call('dingtalk-connector.fixStuckCards', {
412
+ * cardInstanceId: 'card_1713600000000_abc12345',
413
+ * msgId: 'msgXXX',
414
+ * conversationId: 'cidXXX'
415
+ * });
416
+ * ```
417
+ */
418
+ api.registerGatewayMethod('dingtalk-connector.fixStuckCards', async ({ context, params, respond }) => {
419
+ const { loadConfig } = await import('openclaw/plugin-sdk/config-runtime');
420
+ const cfg = loadConfig();
421
+ try {
422
+ const { cardInstanceId, content, msgId, conversationId, accountId } = (params || {}) as any;
423
+ const account = resolveDingtalkAccount({ cfg, accountId: accountId as string | undefined });
424
+
425
+ if (!account.config?.clientId) {
426
+ return respond(false, { error: 'DingTalk not configured' });
427
+ }
428
+
429
+ if (!cardInstanceId && !msgId) {
430
+ return respond(false, {
431
+ error: 'At least one of cardInstanceId or msgId is required',
432
+ usage: {
433
+ cardInstanceId: '(optional) AI Card outTrackId, found in logs like "outTrackId=card_..."',
434
+ content: '(optional) Final card content, defaults to "(回复中断,请重新提问)"',
435
+ msgId: '(optional) Message ID for emotion recall, found in logs like "msgId=..."',
436
+ conversationId: '(optional) Required together with msgId for emotion recall',
437
+ },
438
+ });
439
+ }
440
+
441
+ const results: { card?: { ok: boolean; error?: string }; emotion?: { ok: boolean; error?: string } } = {};
442
+
443
+ // 1. 修复卡住的 AI Card
444
+ if (cardInstanceId) {
445
+ try {
446
+ const { getAccessToken } = await import('./utils/utils-legacy.ts');
447
+ const token = await getAccessToken(account.config);
448
+ const card: AICardInstance = {
449
+ cardInstanceId: String(cardInstanceId),
450
+ accessToken: token,
451
+ tokenExpireTime: Date.now() + 2 * 60 * 60 * 1000,
452
+ inputingStarted: true,
453
+ };
454
+ const finalContent = String(content || '(回复中断,请重新提问)');
455
+ await finishAICard(card, finalContent, account.config, log);
456
+ results.card = { ok: true };
457
+ log?.info?.(`[Gateway][fixStuckCards] AI Card 修复成功: ${cardInstanceId}`);
458
+ } catch (err: any) {
459
+ results.card = { ok: false, error: err.message };
460
+ log?.error?.(`[Gateway][fixStuckCards] AI Card 修复失败: ${err.message}`);
461
+ }
462
+ }
463
+
464
+ // 2. 撤回残留的🤔表情
465
+ if (msgId && conversationId) {
466
+ try {
467
+ await recallEmotionReply(account.config, {
468
+ msgId,
469
+ conversationId,
470
+ robotCode: account.config.clientId,
471
+ }, log);
472
+ results.emotion = { ok: true };
473
+ log?.info?.(`[Gateway][fixStuckCards] 表情撤回成功: msgId=${msgId}`);
474
+ } catch (err: any) {
475
+ results.emotion = { ok: false, error: err.message };
476
+ log?.error?.(`[Gateway][fixStuckCards] 表情撤回失败: ${err.message}`);
477
+ }
478
+ } else if (msgId && !conversationId) {
479
+ results.emotion = { ok: false, error: 'conversationId is required together with msgId' };
480
+ }
481
+
482
+ const allOk = Object.values(results).every(r => r.ok);
483
+ respond(allOk, results);
484
+ } catch (err: any) {
485
+ log?.error?.(`[Gateway][fixStuckCards] 错误: ${err.message}`);
486
+ respond(false, { error: err.message });
487
+ }
488
+ });
489
+
383
490
  api.registerGatewayMethod('dingtalk-connector.probe', async ({ context, respond }) => {
384
491
  const { loadConfig } = await import('openclaw/plugin-sdk/config-runtime');
385
492
  const cfg = loadConfig();
@@ -1,2 +0,0 @@
1
- import { t as registerGatewayMethods } from "./gateway-methods-B3AEEDTe.mjs";
2
- export { registerGatewayMethods };