@dingtalk-real-ai/dingtalk-connector 0.8.21 → 0.8.22

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/bin/dingtalk-connector.js +20 -3
  3. package/dist/entry-bundled.mjs +1 -1
  4. package/dist/{gateway-methods-BNuB2wXl.mjs → gateway-methods-C3nEHxL4.mjs} +2 -2
  5. package/dist/gateway-methods-COXVcCFs.mjs +2 -0
  6. package/dist/index.mjs +2 -2
  7. package/dist/{media-BRqGsKUB.mjs → media-BViJQGgb.mjs} +8 -8
  8. package/dist/{media-DD7Rlljd.mjs → media-CIO05hZn.mjs} +1 -1
  9. package/dist/{message-handler-CPGT1bgU.mjs → message-handler-0NLKAqHU.mjs} +7 -7
  10. package/dist/{messaging-DQwrrd68.mjs → messaging-C2zJ8O-o.mjs} +5 -5
  11. package/dist/{runtime-BphH7_vR.mjs → runtime-BCFW2-1B.mjs} +4 -4
  12. package/dist/{utils-QEvgZ2uM.mjs → utils-DgNm1Ek_.mjs} +7 -5
  13. package/dist/{utils-BqUoUOwd.mjs → utils-TpPdfqWr.mjs} +1 -1
  14. package/docs/RELEASE_NOTES_V0.8.22-beta.0.md +107 -0
  15. package/docs/RELEASE_NOTES_V0.8.22.md +95 -0
  16. package/openclaw.plugin.json +1 -1
  17. package/package.json +1 -1
  18. package/src/core/message-handler.ts +2 -1
  19. package/src/reply-dispatcher.ts +3 -2
  20. package/src/utils/empty-reply.ts +7 -5
  21. package/dist/gateway-methods-B0_tBGPn.mjs +0 -2
  22. package/skills/dingtalk-channel-rules/SKILL.md +0 -91
  23. package/skills/dingtalk-troubleshoot/SKILL.md +0 -93
  24. package/skills/dws-cli/SKILL.md +0 -129
  25. package/skills/dws-cli/references/error-codes.md +0 -95
  26. package/skills/dws-cli/references/field-rules.md +0 -105
  27. package/skills/dws-cli/references/global-reference.md +0 -104
  28. package/skills/dws-cli/references/intent-guide.md +0 -114
  29. package/skills/dws-cli/references/products/aitable.md +0 -452
  30. package/skills/dws-cli/references/products/attendance.md +0 -93
  31. package/skills/dws-cli/references/products/calendar.md +0 -217
  32. package/skills/dws-cli/references/products/chat.md +0 -292
  33. package/skills/dws-cli/references/products/contact.md +0 -108
  34. package/skills/dws-cli/references/products/ding.md +0 -57
  35. package/skills/dws-cli/references/products/report.md +0 -162
  36. package/skills/dws-cli/references/products/simple.md +0 -128
  37. package/skills/dws-cli/references/products/todo.md +0 -138
  38. package/skills/dws-cli/references/products/workbench.md +0 -39
  39. package/skills/dws-cli/references/recovery-guide.md +0 -94
package/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.22] - 2026-05-24
11
+
12
+ 晋升自 `0.8.22-beta.0` 的 GA 版本,与 beta.0 内容完全一致,经过 ~3 天社区验证(无回归反馈)后正式发布。
13
+ GA promotion of `0.8.22-beta.0` after ~3 days of community validation with no regression; functionally identical to the beta.
14
+
15
+ ### 升级 / Upgrade
16
+
17
+ ```bash
18
+ openclaw plugins install @dingtalk-real-ai/dingtalk-connector@0.8.22
19
+ openclaw gateway restart
20
+ ```
21
+
22
+ 以下内容沿用自 `0.8.22-beta.0` 的改进 / Same improvements as `0.8.22-beta.0`:
23
+
24
+ ## [0.8.22-beta.0] - 2026-05-21
25
+
26
+ > **社区验证版本** — 计划 2-3 天观察期后晋升为 `v0.8.22`。详见 [Release Notes](docs/RELEASE_NOTES_V0.8.22-beta.0.md)。
27
+ > Community validation release — planned to promote to `v0.8.22` after a 2-3 day observation window.
28
+
29
+ ### 改进 / Improvements
30
+
31
+ - ✨ **单聊空回复 UX 文案优化 (#599 / #601)** — 把 `✅ 任务执行完成(无文本输出)` 改为口语化的 `好的 👌 有其他问题随时找我`,避免被用户误判为报错。私聊场景下模型对 ACK 类输入选择沉默 / 只走 thinking / 仅工具调用时落到这条兜底,新文案保留"本轮已结束"的信号但去掉系统/技术味;测试改成语义契约(不绑死字符串)。群聊兜底文案与日志 hint 不变。
32
+ **Soften direct-chat empty-reply fallback (#599 / #601)** — Replace `✅ 任务执行完成(无文本输出)` with the conversational `好的 👌 有其他问题随时找我`. The fallback fires when models stay silent on ACK-style inputs / only emit thinking / make tool-only calls; the new copy preserves the "turn ended" signal while dropping the system flavor that misled users into thinking it was an error. Tests refactored to semantic contracts.
33
+
34
+ - ✨ **dws onboarding SSH 兼容 + 版本升级 (#565 / #598)** — `DWS_NPM_PACKAGE` 从 `1.0.13` 升到 npm 最新 `1.0.30`,新装用户拿到 dws #226 修过的 `--help` 文案。onboarding 检测 SSH / 无头环境(`SSH_CLIENT` / `SSH_TTY` / `SSH_CONNECTION`)后,自动把建议命令换成 `dws auth login --device`,避免 127.0.0.1 loopback 在无浏览器服务器上挂死。跨仓根治追踪在 [dws #327](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/327)。
35
+ **dws onboarding SSH compatibility + version bump (#565 / #598)** — Bump `DWS_NPM_PACKAGE` from `1.0.13` to npm latest `1.0.30` so new installs get the dws #226 docs fix. Onboarding now detects SSH / headless env (`SSH_CLIENT` / `SSH_TTY` / `SSH_CONNECTION`) and auto-suggests `dws auth login --device` to avoid 127.0.0.1 loopback hangs on browserless servers. Cross-repo root fix tracked at [dws #327](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/327).
36
+
10
37
  ## [0.8.21] - 2026-05-19
11
38
 
12
39
  晋升自 `0.8.21-beta.0` 的 GA 版本,与 beta.0 内容完全一致,经过社区验证后正式发布。
@@ -398,7 +398,7 @@ function getDwsSpawnEnv() {
398
398
 
399
399
  // ── dws CLI install ─────────────────────────────────────────────
400
400
  const DWS_INSTALL_SCRIPT_URL = 'https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh';
401
- const DWS_NPM_PACKAGE = 'dingtalk-workspace-cli@1.0.13';
401
+ const DWS_NPM_PACKAGE = 'dingtalk-workspace-cli@1.0.30';
402
402
 
403
403
  function isDwsInstalled() {
404
404
  const mod = ['child', 'process'].join('_');
@@ -513,6 +513,23 @@ function isDwsAuthenticated() {
513
513
  }
514
514
  }
515
515
 
516
+ // SSH/无头环境下 dws auth login 默认走 127.0.0.1 loopback 回调,
517
+ // 本地浏览器无法访问远端 loopback,会卡住授权流程(Issue #565 / dws #226)。
518
+ // 检测到 SSH 时引导用户加 --device 走设备流。
519
+ function isSshSession() {
520
+ const env = globalThis['proc' + 'ess'].env;
521
+ return !!(env.SSH_CLIENT || env.SSH_TTY || env.SSH_CONNECTION);
522
+ }
523
+
524
+ function printDwsLoginHint(prefix = ' ') {
525
+ const ssh = isSshSession();
526
+ const cmd = ssh ? 'dws auth login --device' : 'dws auth login';
527
+ console.log(dim(`${prefix}You can also authorize manually anytime: `) + cyan(cmd) + '\n');
528
+ if (ssh) {
529
+ console.log(dim(`${prefix}(检测到 SSH / 无头环境,已切换到 --device 设备流,避免 127.0.0.1 loopback 回调在远端无浏览器时挂起)`) + '\n');
530
+ }
531
+ }
532
+
516
533
  async function ensureDwsCli() {
517
534
  const targetVersion = getTargetDwsVersion();
518
535
 
@@ -562,7 +579,7 @@ async function ensureDwsCli() {
562
579
  console.log(dim(' ✔ dws CLI authenticated') + '\n');
563
580
  } else {
564
581
  console.log(dim(' ℹ dws CLI not yet authenticated. Authorization will be triggered when Agent uses dws features.') + '\n');
565
- console.log(dim(' You can also authorize manually anytime: ') + cyan('dws auth login') + '\n');
582
+ printDwsLoginHint();
566
583
  }
567
584
  return;
568
585
  }
@@ -582,7 +599,7 @@ async function ensureDwsCli() {
582
599
  const freshDisplay = freshVersion ? `v${freshVersion}` : (targetVersion ? `v${targetVersion}` : '');
583
600
  console.log(green(` ✔ dws CLI installed (${freshDisplay})`) + '\n');
584
601
  console.log(dim(' ℹ Authorization will be triggered when Agent uses dws features.') + '\n');
585
- console.log(dim(' You can also authorize manually anytime: ') + cyan('dws auth login') + '\n');
602
+ printDwsLoginHint();
586
603
  }
587
604
 
588
605
  // ── main ───────────────────────────────────────────────────────
@@ -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-B0_tBGPn.mjs");
26
+ const { registerGatewayMethods } = await import("./gateway-methods-COXVcCFs.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
- import { i as DINGTALK_API, o as getAccessToken } from "./utils-QEvgZ2uM.mjs";
4
- import { c as prepareMultiBotMentions, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-DQwrrd68.mjs";
3
+ import { i as DINGTALK_API, o as getAccessToken } from "./utils-DgNm1Ek_.mjs";
4
+ import { c as prepareMultiBotMentions, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-C2zJ8O-o.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 {
@@ -0,0 +1,2 @@
1
+ import { t as registerGatewayMethods } from "./gateway-methods-C3nEHxL4.mjs";
2
+ export { registerGatewayMethods };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-BphH7_vR.mjs";
2
- import { t as registerGatewayMethods } from "./gateway-methods-BNuB2wXl.mjs";
1
+ import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-BCFW2-1B.mjs";
2
+ import { t as registerGatewayMethods } from "./gateway-methods-C3nEHxL4.mjs";
3
3
  //#region index.ts
4
4
  /**
5
5
  * 检测同一 plugin id 在多个路径被加载的情况。
@@ -1,5 +1,5 @@
1
1
  import { n as dingtalkOapiHttp, t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
2
- import { a as DINGTALK_OAPI } from "./utils-QEvgZ2uM.mjs";
2
+ import { a as DINGTALK_OAPI } from "./utils-DgNm1Ek_.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
@@ -287,7 +287,7 @@ async function extractAudioDuration(filePath, log) {
287
287
  */
288
288
  async function sendVideoMessage(config, sessionWebhook, fileName, mediaId, log, metadata) {
289
289
  try {
290
- const token = await (await import("./utils-BqUoUOwd.mjs")).getAccessToken(config);
290
+ const token = await (await import("./utils-TpPdfqWr.mjs")).getAccessToken(config);
291
291
  const videoMessage = {
292
292
  msgtype: "video",
293
293
  video: {
@@ -315,8 +315,8 @@ async function sendVideoMessage(config, sessionWebhook, fileName, mediaId, log,
315
315
  */
316
316
  async function sendVideoProactive(config, target, videoMediaId, picMediaId, metadata, log) {
317
317
  try {
318
- const token = await (await import("./utils-BqUoUOwd.mjs")).getAccessToken(config);
319
- const { DINGTALK_API } = await import("./utils-BqUoUOwd.mjs");
318
+ const token = await (await import("./utils-TpPdfqWr.mjs")).getAccessToken(config);
319
+ const { DINGTALK_API } = await import("./utils-TpPdfqWr.mjs");
320
320
  const msgParam = {
321
321
  duration: metadata?.duration.toString() || "60000",
322
322
  videoMediaId,
@@ -361,8 +361,8 @@ async function sendVideoProactive(config, target, videoMediaId, picMediaId, meta
361
361
  */
362
362
  async function sendAudioProactive(config, target, fileName, mediaId, log, durationMs) {
363
363
  try {
364
- const token = await (await import("./utils-BqUoUOwd.mjs")).getAccessToken(config);
365
- const { DINGTALK_API } = await import("./utils-BqUoUOwd.mjs");
364
+ const token = await (await import("./utils-TpPdfqWr.mjs")).getAccessToken(config);
365
+ const { DINGTALK_API } = await import("./utils-TpPdfqWr.mjs");
366
366
  const msgParam = {
367
367
  mediaId,
368
368
  duration: durationMs && durationMs > 0 ? durationMs.toString() : "60000"
@@ -399,8 +399,8 @@ async function sendAudioProactive(config, target, fileName, mediaId, log, durati
399
399
  */
400
400
  async function sendFileProactive(config, target, fileInfo, mediaId, log) {
401
401
  try {
402
- const token = await (await import("./utils-BqUoUOwd.mjs")).getAccessToken(config);
403
- const { DINGTALK_API } = await import("./utils-BqUoUOwd.mjs");
402
+ const token = await (await import("./utils-TpPdfqWr.mjs")).getAccessToken(config);
403
+ const { DINGTALK_API } = await import("./utils-TpPdfqWr.mjs");
404
404
  const resolvedFileName = fileInfo.fileName || path.basename(fileInfo.path);
405
405
  const msgParam = {
406
406
  mediaId,
@@ -1,2 +1,2 @@
1
- import { a as processVideoMarkers, i as processRawMediaPaths, l as toLocalPath, s as sendFileProactive } from "./media-BRqGsKUB.mjs";
1
+ import { a as processVideoMarkers, i as processRawMediaPaths, l as toLocalPath, s as sendFileProactive } from "./media-BViJQGgb.mjs";
2
2
  export { processRawMediaPaths, processVideoMarkers, sendFileProactive, toLocalPath };
@@ -1,10 +1,10 @@
1
- import { u as uploadMediaToDingTalk } from "./media-BRqGsKUB.mjs";
1
+ import { u as uploadMediaToDingTalk } from "./media-BViJQGgb.mjs";
2
2
  import { a as resolveDingtalkAccount } from "./accounts-CF4oK_HZ.mjs";
3
- import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-BphH7_vR.mjs";
3
+ import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-BCFW2-1B.mjs";
4
4
  import { n as createLoggerFromConfig } from "./logger-BDWwViGT.mjs";
5
5
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
6
- import { n as groupChatLacksVisibleRepliesAutomatic, r as pickEmptyReplyFallbackText, s as getOapiAccessToken, t as emptyGroupReplyLogHint } from "./utils-QEvgZ2uM.mjs";
7
- import { a as sendTextMessage, d as isQpsLimitError, f as streamAICard, i as sendProactive, l as createAICardForTarget, r as sendMessage, t as sendMarkdownMessage, u as finishAICard } from "./messaging-DQwrrd68.mjs";
6
+ import { n as groupChatLacksVisibleRepliesAutomatic, r as pickEmptyReplyFallbackText, s as getOapiAccessToken, t as emptyGroupReplyLogHint } from "./utils-DgNm1Ek_.mjs";
7
+ import { a as sendTextMessage, d as isQpsLimitError, f as streamAICard, i as sendProactive, l as createAICardForTarget, r as sendMessage, t as sendMarkdownMessage, u as finishAICard } from "./messaging-C2zJ8O-o.mjs";
8
8
  import { a as QUEUE_BUSY_ACK_PHRASES, n as normalizeSlashCommand, t as buildSessionContext } from "./session-DJ4jYqPv.mjs";
9
9
  import { d as recallEmotionReply, o as getAccessToken, r as addEmotionReply, s as getOapiAccessToken$1, t as DINGTALK_API } from "./utils-legacy-CALCPP1t.mjs";
10
10
  import "./chunk-upload-6p9cf3UB.mjs";
@@ -364,7 +364,7 @@ function createDingtalkReplyDispatcher(params) {
364
364
  finalText = await processAudioMarkers(finalText, "", account.config, oapiToken, log, true, target);
365
365
  finalText = await uploadAndReplaceFileMarkers(finalText, "", account.config, oapiToken, log, true, target);
366
366
  log.info(`[DingTalk][closeStreaming] 准备调用 processRawMediaPaths`);
367
- const { processRawMediaPaths } = await import("./media-DD7Rlljd.mjs");
367
+ const { processRawMediaPaths } = await import("./media-CIO05hZn.mjs");
368
368
  finalText = await processRawMediaPaths(finalText, account.config, oapiToken, log, target);
369
369
  log.info(`[DingTalk][closeStreaming] processRawMediaPaths 处理完成`);
370
370
  } else log.warn(`[DingTalk][closeStreaming] oapiToken 为空,跳过媒体处理`);
@@ -480,7 +480,7 @@ function createDingtalkReplyDispatcher(params) {
480
480
  const oapiToken = await getOapiAccessToken(account.config);
481
481
  if (oapiToken) {
482
482
  log.info(`[DingTalk][deliver] 检测到 final 响应,准备处理裸露文件路径`);
483
- const { processRawMediaPaths } = await import("./media-DD7Rlljd.mjs");
483
+ const { processRawMediaPaths } = await import("./media-CIO05hZn.mjs");
484
484
  text = await processRawMediaPaths(text, account.config, oapiToken, log, target);
485
485
  log.info(`[DingTalk][deliver] 裸露文件路径处理完成`);
486
486
  }
@@ -1751,7 +1751,7 @@ async function handleDingTalkMessageInternal(params) {
1751
1751
  finalText = await processVideoMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
1752
1752
  finalText = await processAudioMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
1753
1753
  finalText = await uploadAndReplaceFileMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
1754
- const { processRawMediaPaths } = await import("./media-DD7Rlljd.mjs");
1754
+ const { processRawMediaPaths } = await import("./media-CIO05hZn.mjs");
1755
1755
  finalText = await processRawMediaPaths(finalText, config, oapiToken, log, mediaTarget);
1756
1756
  }
1757
1757
  let textToSend = finalText.trim();
@@ -1,7 +1,7 @@
1
- import { u as uploadMediaToDingTalk } from "./media-BRqGsKUB.mjs";
1
+ import { u as uploadMediaToDingTalk } from "./media-BViJQGgb.mjs";
2
2
  import { n as createLoggerFromConfig } from "./logger-BDWwViGT.mjs";
3
3
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
4
- import { i as DINGTALK_API, o as getAccessToken, s as getOapiAccessToken } from "./utils-QEvgZ2uM.mjs";
4
+ import { i as DINGTALK_API, o as getAccessToken, s as getOapiAccessToken } from "./utils-DgNm1Ek_.mjs";
5
5
  import { r as MEDIA_MSG_TYPES } from "./session-DJ4jYqPv.mjs";
6
6
  //#region src/services/messaging/card.ts
7
7
  const AI_CARD_TEMPLATE_ID = "02fcf2f4-5e02-4a85-b672-46d1f715543e.schema";
@@ -851,7 +851,7 @@ async function sendMediaToDingTalk(params) {
851
851
  });
852
852
  }
853
853
  let resolvedMediaUrl = mediaUrl;
854
- const { toLocalPath } = await import("./media-DD7Rlljd.mjs");
854
+ const { toLocalPath } = await import("./media-CIO05hZn.mjs");
855
855
  const _fs = await import("fs");
856
856
  const _path = await import("path");
857
857
  const directPath = toLocalPath(mediaUrl);
@@ -886,7 +886,7 @@ async function sendMediaToDingTalk(params) {
886
886
  }
887
887
  if (mediaType === "video") {
888
888
  const videoMarker = `[DINGTALK_VIDEO]{"path":"${mediaUrl}"}[/DINGTALK_VIDEO]`;
889
- const { processVideoMarkers } = await import("./media-DD7Rlljd.mjs");
889
+ const { processVideoMarkers } = await import("./media-CIO05hZn.mjs");
890
890
  await processVideoMarkers(videoMarker, "", config, oapiToken, console, true, targetParam);
891
891
  if (text?.trim()) {
892
892
  const result = await sendProactive(config, targetParam, text, {
@@ -910,7 +910,7 @@ async function sendMediaToDingTalk(params) {
910
910
  fileName,
911
911
  fileType: ext || "file"
912
912
  };
913
- const { sendFileProactive } = await import("./media-DD7Rlljd.mjs");
913
+ const { sendFileProactive } = await import("./media-CIO05hZn.mjs");
914
914
  await sendFileProactive(config, targetParam, fileInfo, uploadResult.mediaId, log);
915
915
  return {
916
916
  ok: true,
@@ -1,9 +1,9 @@
1
- import { d as __exportAll } from "./media-BRqGsKUB.mjs";
1
+ import { d as __exportAll } from "./media-BViJQGgb.mjs";
2
2
  import { a as resolveDingtalkAccount, c as addWildcardAllowFrom, d as hasConfiguredSecretInput, f as normalizeAccountId, l as createDefaultChannelRuntimeState, m as resolveDefaultGroupPolicy, o as resolveDingtalkCredentials, p as resolveAllowlistProviderRuntimeGroupPolicy, r as resolveDefaultDingtalkAccountId, s as DEFAULT_ACCOUNT_ID, t as listDingtalkAccountIds, u as formatDocsLink } from "./accounts-CF4oK_HZ.mjs";
3
3
  import { t as createLogger } from "./logger-BDWwViGT.mjs";
4
4
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
5
- import "./utils-QEvgZ2uM.mjs";
6
- import { n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-DQwrrd68.mjs";
5
+ import "./utils-DgNm1Ek_.mjs";
6
+ import { n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-C2zJ8O-o.mjs";
7
7
  import { createRequire } from "node:module";
8
8
  import { z, z as z$1 } from "zod";
9
9
  //#region src/secret-input.ts
@@ -905,7 +905,7 @@ async function monitorDingtalkProvider(opts = {}) {
905
905
  const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
906
906
  const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
907
907
  import("./accounts-BSIiLyZa.mjs"),
908
- import("./message-handler-CPGT1bgU.mjs"),
908
+ import("./message-handler-0NLKAqHU.mjs"),
909
909
  import("./connection-D4uO_J9G.mjs")
910
910
  ]);
911
911
  const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
@@ -66,7 +66,7 @@ async function getOapiAccessToken(config) {
66
66
  *
67
67
  * 背景
68
68
  * ----
69
- * 群聊场景下用户 @ 机器人后看到「任务执行完成(无文本输出)」,常见根因不是 connector,
69
+ * 群聊场景下用户 @ 机器人后看到空回复兜底文案,常见根因不是 connector,
70
70
  * 而是上游 OpenClaw 的 reply delivery mode(`source-reply-delivery-mode.ts`):群聊
71
71
  * 默认走 `message_tool_only`,会跳过 `onPartialReply` 与 `accumulatedText`,
72
72
  * 导致本插件累积的文本始终为空,最后落到 connector 的空回复兜底。
@@ -78,11 +78,13 @@ async function getOapiAccessToken(config) {
78
78
  * }
79
79
  * }
80
80
  *
81
- * 本模块的优化目标:让群聊场景看到的兜底文案变成一句可操作的提示,
82
- * 并在日志里给运维一份完整指引,而不是一句无信息量的「任务执行完成」。
83
- * 单聊场景的兜底文案保持原样(单聊空 final 通常是模型自身输出空)。
81
+ * 本模块的优化目标:让兜底文案在两种场景下都自然不"像报错"——
82
+ * - 群聊:返回一段可操作的运维指引(指向 `messages.groupChat.visibleReplies`)。
83
+ * - 单聊:返回一段口语化的简短确认语(单聊空 final 通常是模型自身没产出文本,
84
+ * 如纯思考、只走 tool_call、对 ACK 类输入选择沉默等),并隐含邀请用户继续提问,
85
+ * 避免历史上的「✅ 任务执行完成(无文本输出)」让用户误以为是报错。
84
86
  */
85
- const DIRECT_FALLBACK_TEXT = " 任务执行完成(无文本输出)";
87
+ const DIRECT_FALLBACK_TEXT = "好的 👌 有其他问题随时找我";
86
88
  const GROUP_FALLBACK_TEXT = [
87
89
  "ℹ️ 暂未收到模型回复内容。",
88
90
  "若群聊频繁出现该提示,请联系机器人管理员检查 OpenClaw 配置:",
@@ -1,4 +1,4 @@
1
1
  import "./logger-BDWwViGT.mjs";
2
- import { i as DINGTALK_API, o as getAccessToken } from "./utils-QEvgZ2uM.mjs";
2
+ import { i as DINGTALK_API, o as getAccessToken } from "./utils-DgNm1Ek_.mjs";
3
3
  import "./session-DJ4jYqPv.mjs";
4
4
  export { DINGTALK_API, getAccessToken };
@@ -0,0 +1,107 @@
1
+ # Release Notes - v0.8.22-beta.0
2
+
3
+ > **社区验证版本** — 计划经过 ~2-3 天社区验证后晋升为正式版 `v0.8.22`。
4
+ > **Community validation release** — planned to promote to GA `v0.8.22` after ~2-3 days of community validation.
5
+
6
+ ## 🎉 本次重点 / Highlights
7
+
8
+ 本版本聚焦 UX 文案与 dws onboarding 体验:
9
+
10
+ 1. 把单聊空回复兜底文案 `✅ 任务执行完成(无文本输出)` 换成口语化确认语 `好的 👌 有其他问题随时找我`,避免被用户误判为报错(#599)
11
+ 2. dws CLI 内置版本从 `1.0.13` 升到 npm 最新 `1.0.30`,新装用户拿到正确的 `dws auth login --help` 文案
12
+ 3. onboarding 检测 SSH / 无头环境(`SSH_CLIENT` / `SSH_TTY` / `SSH_CONNECTION`),自动建议 `dws auth login --device`,避免 127.0.0.1 loopback 在远端无浏览器服务器上挂起(#565)
13
+
14
+ This release focuses on UX copy + dws onboarding experience:
15
+
16
+ 1. Replace direct-chat empty-reply fallback `✅ 任务执行完成(无文本输出)` with conversational confirmation `好的 👌 有其他问题随时找我`, so users no longer mistake it for an error (#599)
17
+ 2. Bump pinned dws CLI from `1.0.13` to npm latest `1.0.30`; new installs get the corrected `dws auth login --help` copy
18
+ 3. Detect SSH / headless env in onboarding and auto-suggest `dws auth login --device`, avoiding 127.0.0.1 loopback hangs on remote headless servers (#565)
19
+
20
+ ## ✨ 改进 / Improvements
21
+
22
+ ### 单聊空回复 UX 文案优化 (#599 / #601)
23
+
24
+ **现象**:用户对一段说明回「知道了」后,机器人显示 `✅ 任务执行完成(无文本输出)`,被误判为报错。
25
+
26
+ **根因**:私聊场景下模型可能因 ACK 类输入选择沉默(只走 thinking / tool_call、或纯输出空文本)。connector 的空回复兜底文案系统/技术味偏重,让用户以为是异常。
27
+
28
+ **改动**:
29
+
30
+ - `src/utils/empty-reply.ts:23` — `DIRECT_FALLBACK_TEXT` 改为 `好的 👌 有其他问题随时找我`,保留"本轮已结束"信号但去掉技术味
31
+ - 测试改成语义契约(不绑死字符串):不出现报错感字样 / 以「好」开头 / 包含追问引导 / 与群聊文案不同
32
+ - 群聊兜底文案与日志 hint 维持不变(仍是面向运维的可操作指引)
33
+
34
+ **Phenomenon**: After replying "知道了" (got it) to a bot's explanation, users saw `✅ 任务执行完成(无文本输出)` and mistook it for an error.
35
+
36
+ **Root cause**: In direct chat, models may choose to stay silent on ACK-style input (only thinking / tool_call, or empty text output). The connector's fallback copy was too system-y, leading users to misjudge it as a fault.
37
+
38
+ **Changes**:
39
+
40
+ - `src/utils/empty-reply.ts:23` — `DIRECT_FALLBACK_TEXT` becomes `好的 👌 有其他问题随时找我`, keeping the "turn ended" signal while dropping the system flavor
41
+ - Tests refactored to semantic contracts (no hardcoded strings): no error-flavored words / starts with 「好」 / contains a follow-up invitation / differs from group fallback
42
+ - Group-chat fallback copy and log hint unchanged (still actionable ops guidance)
43
+
44
+ ### dws onboarding SSH 兼容 + 版本升级 (#565 / #598)
45
+
46
+ **现象**:SSH / 无头服务器上首次 `dws auth login` 卡死——dws CLI 默认走 127.0.0.1 loopback 回调,本地浏览器无法访问远端 loopback。
47
+
48
+ **根因**:connector pin 的 dws 是 `1.0.13`,此版本 `--help` 文案描述与实际行为相反,SSH 用户照着 help 跑必然踩坑。
49
+
50
+ **改动**:
51
+
52
+ - `bin/dingtalk-connector.js:401` — `DWS_NPM_PACKAGE` 从 `1.0.13` → `1.0.30`(npm latest,含上游 dws #226 文档修复)
53
+ - 新增 `isSshSession()`:检测 `SSH_CLIENT` / `SSH_TTY` / `SSH_CONNECTION` 三个环境变量
54
+ - 新增 `printDwsLoginHint()` 辅助:SSH 命中时把 `dws auth login` 换成 `dws auth login --device`,并附一句说明
55
+ - 把"已安装/全新安装"两条路径的登录提示统一走 `printDwsLoginHint()`,避免分叉
56
+
57
+ **根治方向(跨仓 follow-up)**:
58
+
59
+ - [dws #327](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/327) — 建议 dws 自身检测 SSH 环境自动降级到 `--device`,根治后 connector 这边的兜底逻辑可以择机删除
60
+ - 「对话框内授权」是更大的跨仓 UX 改造,需要 dws 支持非 CLI 流程或 Web flow,本版本不涉及
61
+
62
+ **Phenomenon**: First-time `dws auth login` hangs on SSH / headless servers — the dws CLI defaults to a 127.0.0.1 loopback callback that the local browser can't reach.
63
+
64
+ **Root cause**: connector pinned dws `1.0.13` whose `--help` text described the opposite of actual behavior; SSH users following the help inevitably hit the trap.
65
+
66
+ **Changes**:
67
+
68
+ - `bin/dingtalk-connector.js:401` — `DWS_NPM_PACKAGE` from `1.0.13` → `1.0.30` (npm latest, including upstream dws #226 docs fix)
69
+ - Add `isSshSession()`: detects `SSH_CLIENT` / `SSH_TTY` / `SSH_CONNECTION` env vars
70
+ - Add `printDwsLoginHint()` helper: when SSH is detected, suggest `dws auth login --device` with a brief reason
71
+ - Unify login hint between "already installed" and "fresh install" branches via `printDwsLoginHint()` to avoid drift
72
+
73
+ **Long-term root fix (cross-repo follow-up)**:
74
+
75
+ - [dws #327](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/327) — propose dws itself detect SSH and auto-fall back to `--device`. Once that lands, the connector-side workaround can be removed.
76
+ - "In-chat authorization" is a larger cross-repo UX overhaul (needs dws to support non-CLI / Web flow); not in scope for this release.
77
+
78
+ ## 🧪 验证 / Validation
79
+
80
+ - `tests/empty-reply.unit.test.ts` — 9/9 单测通过(语义契约风格)
81
+ - `node --check bin/dingtalk-connector.js` — 语法通过
82
+ - 手动验证:设置 `SSH_CLIENT` 环境变量后,onboarding 提示自动切到 `dws auth login --device`
83
+
84
+ ## 📦 升级方式 / How to upgrade
85
+
86
+ ```bash
87
+ openclaw plugins install @dingtalk-real-ai/dingtalk-connector@0.8.22-beta.0
88
+ openclaw gateway restart
89
+ ```
90
+
91
+ 或者:
92
+
93
+ ```bash
94
+ npm install -g @dingtalk-real-ai/dingtalk-connector@0.8.22-beta.0
95
+ ```
96
+
97
+ ## ⏭️ 后续节奏 / Next steps
98
+
99
+ - **2026-05-22 ~ 2026-05-24**:社区使用反馈窗口,2-3 天观察期
100
+ - **~2026-05-24 之后**:若无回归,晋升为正式版 `v0.8.22`
101
+ - 升级遇到问题请提交到 [Issues](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues),按置顶 [#584](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/584) / [#585](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/585) 的模板补全反馈信息
102
+
103
+ ## 🔗 关联 / References
104
+
105
+ - Issue [#599](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/599) → PR [#601](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/pull/601)
106
+ - Issue [#565](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/565) → PR [#598](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/pull/598)
107
+ - 跨仓 follow-up: [dws #327](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/327)
@@ -0,0 +1,95 @@
1
+ # Release Notes - v0.8.22
2
+
3
+ > **GA 正式版** — 晋升自 `0.8.22-beta.0`,经 ~3 天社区验证(无回归反馈)后正式发布。
4
+ > **General Availability** — Promoted from `0.8.22-beta.0` after ~3 days of community validation with no regression.
5
+
6
+ ## 🎉 本次重点 / Highlights
7
+
8
+ 本版本聚焦 UX 文案与 dws onboarding 体验,与 `0.8.22-beta.0` 功能完全一致:
9
+
10
+ 1. 把单聊空回复兜底文案 `✅ 任务执行完成(无文本输出)` 换成口语化的 `好的 👌 有其他问题随时找我`,避免被用户误判为报错([#599](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/599) / [PR #601](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/pull/601))
11
+ 2. 内置 dws CLI 从 `1.0.13` 升到 npm 最新 `1.0.30`,新装用户拿到正确的 `dws auth login --help` 文案
12
+ 3. onboarding 检测 SSH / 无头环境(`SSH_CLIENT` / `SSH_TTY` / `SSH_CONNECTION`),自动建议 `dws auth login --device`,避免 127.0.0.1 loopback 在远端无浏览器服务器上挂起([#565](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/565) / [PR #598](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/pull/598))
13
+
14
+ This release focuses on UX copy + dws onboarding experience. Functionally identical to `0.8.22-beta.0`:
15
+
16
+ 1. Replace direct-chat empty-reply fallback `✅ 任务执行完成(无文本输出)` with conversational `好的 👌 有其他问题随时找我`, so users no longer mistake it for an error (#599 / PR #601)
17
+ 2. Bump bundled dws CLI from `1.0.13` to npm latest `1.0.30`; new installs get the corrected `dws auth login --help` copy
18
+ 3. Detect SSH / headless env in onboarding and auto-suggest `dws auth login --device`, avoiding 127.0.0.1 loopback hangs on remote headless servers (#565 / PR #598)
19
+
20
+ ## ✨ 改进 / Improvements
21
+
22
+ ### 单聊空回复 UX 文案优化 ([#599](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/599) / [PR #601](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/pull/601))
23
+
24
+ **现象**:用户对一段说明回「知道了」后,机器人显示 `✅ 任务执行完成(无文本输出)`,被误判为报错。
25
+
26
+ **根因**:私聊场景下模型可能因 ACK 类输入选择沉默(只走 thinking / tool_call、或纯输出空文本)。connector 的空回复兜底文案系统/技术味偏重。
27
+
28
+ **改动**:
29
+
30
+ - `src/utils/empty-reply.ts:23` — `DIRECT_FALLBACK_TEXT` 改为 `好的 👌 有其他问题随时找我`,保留"本轮已结束"信号但去掉技术味
31
+ - 测试改成语义契约(不绑死字符串):不出现报错感字样 / 以「好」开头 / 包含追问引导 / 与群聊文案不同
32
+ - 群聊兜底文案与日志 hint 维持不变(仍是面向运维的可操作指引)
33
+
34
+ ### dws onboarding SSH 兼容 + 版本升级 ([#565](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/565) / [PR #598](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/pull/598))
35
+
36
+ **现象**:SSH / 无头服务器上首次 `dws auth login` 卡死——dws CLI 默认走 127.0.0.1 loopback 回调,本地浏览器无法访问远端 loopback。
37
+
38
+ **根因**:connector pin 的 dws 是 `1.0.13`,此版本 `--help` 文案描述与实际行为相反,SSH 用户照着 help 跑必然踩坑。
39
+
40
+ **改动**:
41
+
42
+ - `bin/dingtalk-connector.js:401` — `DWS_NPM_PACKAGE` 从 `1.0.13` → `1.0.30`(npm latest,含上游 dws #226 文档修复)
43
+ - 新增 `isSshSession()`:检测 `SSH_CLIENT` / `SSH_TTY` / `SSH_CONNECTION` 三个环境变量
44
+ - 新增 `printDwsLoginHint()` 辅助:SSH 命中时把 `dws auth login` 换成 `dws auth login --device`,并附一句说明
45
+ - 把"已安装/全新安装"两条路径的登录提示统一走 `printDwsLoginHint()`,避免分叉
46
+
47
+ **根治方向(跨仓 follow-up)**:
48
+
49
+ - [dws #327](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/327) — 建议 dws 自身检测 SSH 环境自动降级到 `--device`,根治后 connector 这边的兜底逻辑可以择机删除
50
+ - 「对话框内授权」是更大的跨仓 UX 改造,需要 dws 支持非 CLI 流程或 Web flow,本版本不涉及
51
+
52
+ ## 🔒 兼容性 / Compatibility
53
+
54
+ - **API 无变化**、配置 schema 无变化、导出符号无变化
55
+ - 现有用户升级无需任何配置改动
56
+ - 群聊行为完全不变(空回复兜底文案与日志 hint 未动)
57
+ - 仅以下两类用户感知到差异:
58
+ - 私聊场景看到空回复兜底文案的用户 —— 文案更友好
59
+ - 全新安装 / 在 SSH 环境首次 `dws auth login` 的用户 —— 自动得到正确命令建议
60
+
61
+ ## 🧪 验证 / Verification
62
+
63
+ **Beta 社区验证(2026-05-21 ~ 2026-05-24,~3 天)**:
64
+ - npm `beta` tag 上 `0.8.22-beta.0` 稳定可用
65
+ - 无任何针对私聊空回复文案 / dws SSH onboarding 的回归 issue
66
+
67
+ **已验证组合 / Verified combo**:
68
+ - OpenClaw Gateway `2026.5.12` (f066dd2)
69
+ - Connector `0.8.22-beta.0`(已晋升为 `0.8.22`)
70
+ - 平台 macOS(darwin 23.2.0)
71
+
72
+ ## 📥 安装升级 / Installation & Upgrade
73
+
74
+ ```bash
75
+ openclaw plugins install @dingtalk-real-ai/dingtalk-connector@0.8.22
76
+ openclaw gateway restart
77
+ ```
78
+
79
+ 或:
80
+
81
+ ```bash
82
+ npm install @dingtalk-real-ai/dingtalk-connector@latest
83
+ ```
84
+
85
+ ## 🔗 相关链接 / Related Links
86
+
87
+ - [完整变更日志 / Full Changelog](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/CHANGELOG.md)
88
+ - [Beta release notes (`v0.8.22-beta.0`)](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/docs/RELEASE_NOTES_V0.8.22-beta.0.md)
89
+ - 关联 PRs / issues:[#599](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/599) / [#601](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/pull/601) / [#565](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues/565) / [#598](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/pull/598) / [dws #327](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/327)
90
+
91
+ ---
92
+
93
+ **发布日期 / Release Date**:2026-05-24
94
+ **版本号 / Version**:v0.8.22
95
+ **兼容性 / Compatibility**:OpenClaw Gateway 2026.5.7+
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dingtalk-connector",
3
3
  "name": "DingTalk Channel",
4
- "version": "0.8.21",
4
+ "version": "0.8.22",
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.21",
3
+ "version": "0.8.22",
4
4
  "description": "Official OpenClaw DingTalk channel plugin | 钉钉官方 OpenClaw 插件",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1579,7 +1579,8 @@ export async function handleDingTalkMessageInternal(params: HandleMessageParams)
1579
1579
 
1580
1580
  // ✅ 异步模式下 final 文本为空时的兜底
1581
1581
  // 群聊场景下,常见根因是 OpenClaw `messages.groupChat.visibleReplies` 未设为 "automatic"
1582
- // (详见 src/utils/empty-reply.ts),给运维一份可操作的指引而不是无信息量的「任务执行完成」。
1582
+ // (详见 src/utils/empty-reply.ts),给运维一份可操作的指引;
1583
+ // 单聊则用口语化确认语兜底,避免被用户误判为报错。
1583
1584
  let textToSend = finalText.trim();
1584
1585
  if (!textToSend) {
1585
1586
  const isGroup = !isDirect;
@@ -308,8 +308,9 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
308
308
  // ✅ 如果累积的文本为空,使用默认提示文案
309
309
  // 群聊场景下,常见根因是 OpenClaw `messages.groupChat.visibleReplies` 未设为
310
310
  // "automatic"(上游 source-reply-delivery-mode.ts 走 message_tool_only 时
311
- // 会跳过 onPartialReply,accumulatedText 始终为空)。给运维一份可操作的指引,
312
- // 而不是一句无信息量的「任务执行完成」。详见 src/utils/empty-reply.ts。
311
+ // 会跳过 onPartialReply,accumulatedText 始终为空)。给运维一份可操作的指引;
312
+ // 单聊则用口语化确认语兜底,避免「任务执行完成(无文本输出)」让用户误判为报错。
313
+ // 详见 src/utils/empty-reply.ts。
313
314
  if (!finalText.trim()) {
314
315
  const isGroup = !isDirect;
315
316
  finalText = pickEmptyReplyFallbackText(isGroup);
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 背景
5
5
  * ----
6
- * 群聊场景下用户 @ 机器人后看到「任务执行完成(无文本输出)」,常见根因不是 connector,
6
+ * 群聊场景下用户 @ 机器人后看到空回复兜底文案,常见根因不是 connector,
7
7
  * 而是上游 OpenClaw 的 reply delivery mode(`source-reply-delivery-mode.ts`):群聊
8
8
  * 默认走 `message_tool_only`,会跳过 `onPartialReply` 与 `accumulatedText`,
9
9
  * 导致本插件累积的文本始终为空,最后落到 connector 的空回复兜底。
@@ -15,12 +15,14 @@
15
15
  * }
16
16
  * }
17
17
  *
18
- * 本模块的优化目标:让群聊场景看到的兜底文案变成一句可操作的提示,
19
- * 并在日志里给运维一份完整指引,而不是一句无信息量的「任务执行完成」。
20
- * 单聊场景的兜底文案保持原样(单聊空 final 通常是模型自身输出空)。
18
+ * 本模块的优化目标:让兜底文案在两种场景下都自然不"像报错"——
19
+ * - 群聊:返回一段可操作的运维指引(指向 `messages.groupChat.visibleReplies`)。
20
+ * - 单聊:返回一段口语化的简短确认语(单聊空 final 通常是模型自身没产出文本,
21
+ * 如纯思考、只走 tool_call、对 ACK 类输入选择沉默等),并隐含邀请用户继续提问,
22
+ * 避免历史上的「✅ 任务执行完成(无文本输出)」让用户误以为是报错。
21
23
  */
22
24
 
23
- const DIRECT_FALLBACK_TEXT = ' 任务执行完成(无文本输出)';
25
+ const DIRECT_FALLBACK_TEXT = '好的 👌 有其他问题随时找我';
24
26
 
25
27
  const GROUP_FALLBACK_TEXT = [
26
28
  'ℹ️ 暂未收到模型回复内容。',
@@ -1,2 +0,0 @@
1
- import { t as registerGatewayMethods } from "./gateway-methods-BNuB2wXl.mjs";
2
- export { registerGatewayMethods };