@soimy/dingtalk 3.6.11 → 3.7.0

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 (161) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +11684 -10583
  3. package/dist/src/{ack-reaction-service.d.ts → ack-reaction/ack-reaction-service.d.ts} +1 -1
  4. package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts +3 -3
  5. package/dist/src/card/ask-user-question-context.d.ts +1 -1
  6. package/dist/src/card/ask-user-question-store.d.ts +1 -1
  7. package/dist/src/card/ask-user-question.d.ts +1 -1
  8. package/dist/src/card/card-action-handler.d.ts +2 -2
  9. package/dist/src/{card-draft-controller.d.ts → card/card-draft-controller.d.ts} +5 -1
  10. package/dist/src/card/card-run-registry.d.ts +2 -2
  11. package/dist/src/{card-service.d.ts → card/card-service.d.ts} +1 -1
  12. package/dist/src/card/card-stop-handler.d.ts +1 -1
  13. package/dist/src/card/card-streaming-mode.d.ts +1 -1
  14. package/dist/src/card/card-task-progress.d.ts +60 -0
  15. package/dist/src/channel.d.ts +6 -6
  16. package/dist/src/command/card-stop-command.d.ts +1 -1
  17. package/dist/src/{feedback-learning-service.d.ts → command/feedback-learning-service.d.ts} +1 -1
  18. package/dist/src/command/inbound-command-dispatch-service.d.ts +2 -2
  19. package/dist/src/{learning-command-service.d.ts → command/learning-command-service.d.ts} +1 -1
  20. package/dist/src/gateway/channel-gateway.d.ts +1 -1
  21. package/dist/src/{connection-manager.d.ts → gateway/connection-manager.d.ts} +1 -1
  22. package/dist/src/{docs-service.d.ts → gateway/docs-service.d.ts} +1 -1
  23. package/dist/src/{inbound-handler.d.ts → gateway/inbound-handler.d.ts} +1 -1
  24. package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts +1 -1
  25. package/dist/src/messaging/attachment-text-extractor.d.ts +1 -1
  26. package/dist/src/messaging/btw-deliver.d.ts +1 -1
  27. package/dist/src/messaging/channel-outbound.d.ts +1 -1
  28. package/dist/src/{media-utils.d.ts → messaging/media-utils.d.ts} +1 -1
  29. package/dist/src/{message-context-store.d.ts → messaging/message-context-store.d.ts} +7 -1
  30. package/dist/src/{message-utils.d.ts → messaging/message-utils.d.ts} +1 -1
  31. package/dist/src/messaging/quoted-context.d.ts +2 -2
  32. package/dist/src/messaging/quoted-file-service.d.ts +1 -1
  33. package/dist/src/messaging/quoted-ref.d.ts +2 -2
  34. package/dist/src/{reply-strategy-card.d.ts → messaging/reply-strategy-card.d.ts} +1 -1
  35. package/dist/src/{reply-strategy-types.d.ts → messaging/reply-strategy-types.d.ts} +10 -1
  36. package/dist/src/{reply-strategy-with-reaction.d.ts → messaging/reply-strategy-with-reaction.d.ts} +1 -1
  37. package/dist/src/{reply-strategy.d.ts → messaging/reply-strategy.d.ts} +1 -1
  38. package/dist/src/{send-service.d.ts → messaging/send-service.d.ts} +1 -1
  39. package/dist/src/platform/channel-status.d.ts +1 -1
  40. package/dist/src/platform/config.d.ts +110 -0
  41. package/dist/src/platform/runtime-events.d.ts +67 -0
  42. package/dist/src/{types.d.ts → platform/types.d.ts} +74 -1
  43. package/dist/src/{persistence-store.d.ts → shared/persistence-store.d.ts} +1 -1
  44. package/dist/src/{utils.d.ts → shared/utils.d.ts} +1 -1
  45. package/dist/src/targeting/agent-name-matcher.d.ts +1 -1
  46. package/dist/src/targeting/agent-routing.d.ts +3 -3
  47. package/dist/src/{session-routing.d.ts → targeting/session-routing.d.ts} +1 -1
  48. package/index.ts +193 -69
  49. package/openclaw.plugin.json +158 -2
  50. package/package.json +16 -16
  51. package/src/{ack-reaction-classifier.ts → ack-reaction/ack-reaction-classifier.ts} +85 -13
  52. package/src/{ack-reaction-service.ts → ack-reaction/ack-reaction-service.ts} +13 -7
  53. package/src/ack-reaction/dynamic-ack-reaction-controller.ts +46 -31
  54. package/src/ack-reaction/dynamic-ack-reaction-progress.ts +4 -1
  55. package/src/card/ask-user-question-context.ts +1 -1
  56. package/src/card/ask-user-question-store.ts +2 -2
  57. package/src/card/ask-user-question.ts +11 -7
  58. package/src/card/card-action-handler.ts +2 -2
  59. package/src/{card-callback-service.ts → card/card-callback-service.ts} +7 -9
  60. package/src/card/card-draft-controller.ts +770 -0
  61. package/src/card/card-markdown-image-reroute.ts +8 -2
  62. package/src/card/card-run-registry.ts +17 -7
  63. package/src/{card-service.ts → card/card-service.ts} +91 -61
  64. package/src/card/card-stop-handler.ts +12 -8
  65. package/src/card/card-streaming-mode.ts +1 -1
  66. package/src/card/card-task-progress.ts +330 -0
  67. package/src/card/draft-stream-loop.ts +119 -0
  68. package/src/card/reasoning-answer-split.ts +125 -121
  69. package/src/card/reasoning-block-assembler.ts +122 -123
  70. package/src/{run-usage-store.ts → card/run-usage-store.ts} +18 -6
  71. package/src/card/statusline-renderer.ts +14 -5
  72. package/src/card/task-model-metadata.ts +8 -3
  73. package/src/channel.ts +14 -16
  74. package/src/command/card-stop-command.ts +2 -2
  75. package/src/{feedback-learning-service.ts → command/feedback-learning-service.ts} +36 -16
  76. package/src/{feedback-learning-store.ts → command/feedback-learning-store.ts} +133 -81
  77. package/src/command/inbound-command-dispatch-service.ts +12 -6
  78. package/src/{learning-command-service.ts → command/learning-command-service.ts} +36 -29
  79. package/src/{session-command-service.ts → command/session-command-service.ts} +3 -1
  80. package/src/gateway/channel-gateway.ts +106 -28
  81. package/src/{connection-manager.ts → gateway/connection-manager.ts} +45 -30
  82. package/src/{docs-service.ts → gateway/docs-service.ts} +5 -5
  83. package/src/{inbound-handler.ts → gateway/inbound-handler.ts} +139 -84
  84. package/src/gateway/inbound-session-queue-dispatcher.ts +18 -14
  85. package/src/{session-lock.ts → gateway/session-lock.ts} +14 -14
  86. package/src/messaging/attachment-text-extractor.ts +21 -7
  87. package/src/messaging/btw-deliver.ts +2 -2
  88. package/src/messaging/channel-actions.ts +15 -7
  89. package/src/messaging/channel-outbound.ts +16 -11
  90. package/src/{media-utils.ts → messaging/media-utils.ts} +276 -72
  91. package/src/{message-context-store.ts → messaging/message-context-store.ts} +11 -2
  92. package/src/{message-utils.ts → messaging/message-utils.ts} +46 -11
  93. package/src/messaging/quoted-context.ts +2 -2
  94. package/src/messaging/quoted-file-service.ts +311 -294
  95. package/src/messaging/quoted-ref.ts +11 -7
  96. package/src/{reply-strategy-card.ts → messaging/reply-strategy-card.ts} +178 -101
  97. package/src/{reply-strategy-markdown.ts → messaging/reply-strategy-markdown.ts} +8 -2
  98. package/src/{reply-strategy-types.ts → messaging/reply-strategy-types.ts} +11 -1
  99. package/src/{reply-strategy-with-reaction.ts → messaging/reply-strategy-with-reaction.ts} +6 -1
  100. package/src/{reply-strategy.ts → messaging/reply-strategy.ts} +2 -2
  101. package/src/{send-service.ts → messaging/send-service.ts} +100 -65
  102. package/src/{auth.ts → platform/auth.ts} +2 -2
  103. package/src/platform/channel-status.ts +3 -3
  104. package/src/{config-schema.ts → platform/config-schema.ts} +81 -4
  105. package/src/{config.ts → platform/config.ts} +167 -19
  106. package/src/{device-registration.ts → platform/device-registration.ts} +5 -4
  107. package/src/{onboarding.ts → platform/onboarding.ts} +1 -4
  108. package/src/{plugin-sdk-channel-actions-augment.ts → platform/plugin-sdk-channel-actions-augment.ts} +6 -5
  109. package/src/platform/runtime-events.ts +202 -0
  110. package/src/{secret-input.ts → platform/secret-input.ts} +117 -11
  111. package/src/{types.ts → platform/types.ts} +76 -1
  112. package/src/{http-client.ts → shared/http-client.ts} +1 -3
  113. package/src/{persistence-store.ts → shared/persistence-store.ts} +6 -6
  114. package/src/{utils.ts → shared/utils.ts} +30 -13
  115. package/src/targeting/agent-name-matcher.ts +1 -1
  116. package/src/targeting/agent-routing.ts +7 -7
  117. package/src/targeting/group-members-store.ts +1 -1
  118. package/src/{session-peer-store.ts → targeting/session-peer-store.ts} +9 -4
  119. package/src/{session-routing.ts → targeting/session-routing.ts} +1 -1
  120. package/src/targeting/target-directory-adapter.ts +3 -3
  121. package/src/targeting/target-directory-store.ts +1 -1
  122. package/src/targeting/target-input.ts +1 -1
  123. package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts +0 -35
  124. package/dist/src/config.d.ts +0 -59
  125. package/src/ack-reaction/dynamic-ack-reaction-events.ts +0 -123
  126. package/src/card-draft-controller.ts +0 -637
  127. package/src/draft-stream-loop.ts +0 -119
  128. /package/dist/src/{ack-reaction-classifier.d.ts → ack-reaction/ack-reaction-classifier.d.ts} +0 -0
  129. /package/dist/src/{card-callback-service.d.ts → card/card-callback-service.d.ts} +0 -0
  130. /package/dist/src/{draft-stream-loop.d.ts → card/draft-stream-loop.d.ts} +0 -0
  131. /package/dist/src/{run-usage-store.d.ts → card/run-usage-store.d.ts} +0 -0
  132. /package/dist/src/{feedback-learning-store.d.ts → command/feedback-learning-store.d.ts} +0 -0
  133. /package/dist/src/{session-command-service.d.ts → command/session-command-service.d.ts} +0 -0
  134. /package/dist/src/{session-lock.d.ts → gateway/session-lock.d.ts} +0 -0
  135. /package/dist/src/{proactive-risk-registry.d.ts → messaging/proactive-risk-registry.d.ts} +0 -0
  136. /package/dist/src/{reply-strategy-markdown.d.ts → messaging/reply-strategy-markdown.d.ts} +0 -0
  137. /package/dist/src/{access-control.d.ts → platform/access-control.d.ts} +0 -0
  138. /package/dist/src/{auth.d.ts → platform/auth.d.ts} +0 -0
  139. /package/dist/src/{config-schema.d.ts → platform/config-schema.d.ts} +0 -0
  140. /package/dist/src/{device-registration.d.ts → platform/device-registration.d.ts} +0 -0
  141. /package/dist/src/{logger-context.d.ts → platform/logger-context.d.ts} +0 -0
  142. /package/dist/src/{onboarding.d.ts → platform/onboarding.d.ts} +0 -0
  143. /package/dist/src/{plugin-sdk-channel-actions-augment.d.ts → platform/plugin-sdk-channel-actions-augment.d.ts} +0 -0
  144. /package/dist/src/{runtime.d.ts → platform/runtime.d.ts} +0 -0
  145. /package/dist/src/{secret-input.d.ts → platform/secret-input.d.ts} +0 -0
  146. /package/dist/src/{session-state.d.ts → platform/session-state.d.ts} +0 -0
  147. /package/dist/src/{signature.d.ts → platform/signature.d.ts} +0 -0
  148. /package/dist/src/{dedup.d.ts → shared/dedup.d.ts} +0 -0
  149. /package/dist/src/{http-client.d.ts → shared/http-client.d.ts} +0 -0
  150. /package/dist/src/{path-utils.d.ts → shared/path-utils.d.ts} +0 -0
  151. /package/dist/src/{peer-id-registry.d.ts → targeting/peer-id-registry.d.ts} +0 -0
  152. /package/dist/src/{session-peer-store.d.ts → targeting/session-peer-store.d.ts} +0 -0
  153. /package/src/{proactive-risk-registry.ts → messaging/proactive-risk-registry.ts} +0 -0
  154. /package/src/{access-control.ts → platform/access-control.ts} +0 -0
  155. /package/src/{logger-context.ts → platform/logger-context.ts} +0 -0
  156. /package/src/{runtime.ts → platform/runtime.ts} +0 -0
  157. /package/src/{session-state.ts → platform/session-state.ts} +0 -0
  158. /package/src/{signature.ts → platform/signature.ts} +0 -0
  159. /package/src/{dedup.ts → shared/dedup.ts} +0 -0
  160. /package/src/{path-utils.ts → shared/path-utils.ts} +0 -0
  161. /package/src/{peer-id-registry.ts → targeting/peer-id-registry.ts} +0 -0
@@ -1,46 +1,42 @@
1
1
  import * as path from "node:path";
2
- import axios from "./http-client";
3
- import { getAccessToken } from "./auth";
4
- import { resolveCardRunByConversation, resolveCardRunByOwner } from "./card/card-run-registry";
5
-
6
- import {
7
- isCardInTerminalState,
8
- sendProactiveCardText,
9
- } from "./card-service";
10
- import { resolveRobotCode, stripTargetPrefix } from "./config";
11
- import { getLogger } from "./logger-context";
2
+ import { resolveCardRunByConversation, resolveCardRunByOwner } from "../card/card-run-registry";
3
+ import { isCardInTerminalState, sendProactiveCardText } from "../card/card-service";
4
+ import { getAccessToken } from "../platform/auth";
5
+ import { resolveRobotCode, stripTargetPrefix } from "../platform/config";
6
+ import { getLogger } from "../platform/logger-context";
7
+ import type {
8
+ AICardInstance,
9
+ AxiosResponse,
10
+ DingTalkConfig,
11
+ DingTalkTrackingMetadata,
12
+ Logger,
13
+ ProactiveMessagePayload,
14
+ QuotedRef,
15
+ SendMessageOptions,
16
+ SessionWebhookResponse,
17
+ } from "../platform/types";
18
+ import axios from "../shared/http-client";
19
+ import { formatDingTalkErrorPayloadLog, getProxyBypassOption } from "../shared/utils";
20
+ import { resolveOriginalPeerId } from "../targeting/peer-id-registry";
12
21
  import {
13
22
  getVoiceDurationMs,
14
23
  prepareMediaInput,
15
24
  resolveOutboundMediaType,
16
25
  uploadMedia as uploadMediaUtil,
17
26
  } from "./media-utils";
18
- import { convertMarkdownTablesToPlainText, detectMarkdownAndExtractTitle } from "./message-utils";
27
+ import type { UploadMediaResult } from "./media-utils";
19
28
  import {
20
29
  DEFAULT_MESSAGE_CONTEXT_TTL_DAYS,
21
30
  DEFAULT_OUTBOUND_SENDER,
22
31
  inferConversationChatType,
23
32
  upsertOutboundMessageContext,
24
33
  } from "./message-context-store";
25
- import { resolveOriginalPeerId } from "./peer-id-registry";
34
+ import { convertMarkdownTablesToPlainText, detectMarkdownAndExtractTitle } from "./message-utils";
26
35
  import {
27
36
  deleteProactiveRiskObservation,
28
37
  getProactiveRiskObservation,
29
38
  recordProactiveRiskObservation,
30
39
  } from "./proactive-risk-registry";
31
- import { formatDingTalkErrorPayloadLog, getProxyBypassOption } from "./utils";
32
- import type { UploadMediaResult } from "./media-utils";
33
- import type {
34
- AICardInstance,
35
- AxiosResponse,
36
- DingTalkConfig,
37
- DingTalkTrackingMetadata,
38
- Logger,
39
- ProactiveMessagePayload,
40
- QuotedRef,
41
- SendMessageOptions,
42
- SessionWebhookResponse,
43
- } from "./types";
44
40
 
45
41
  export { detectMediaTypeFromExtension } from "./media-utils";
46
42
 
@@ -93,7 +89,9 @@ async function replaceMarkdownLocalImages(params: {
93
89
 
94
90
  type ProactiveTextSendResult = AxiosResponse | { tracking: DingTalkTrackingMetadata };
95
91
 
96
- function isTrackingResult(result: ProactiveTextSendResult): result is { tracking: DingTalkTrackingMetadata } {
92
+ function isTrackingResult(
93
+ result: ProactiveTextSendResult,
94
+ ): result is { tracking: DingTalkTrackingMetadata } {
97
95
  return "tracking" in result;
98
96
  }
99
97
 
@@ -120,7 +118,12 @@ function extractOutboundDeliveryMetadata(payload: unknown): {
120
118
  data.tracking && typeof data.tracking === "object"
121
119
  ? (data.tracking as Record<string, unknown>)
122
120
  : undefined;
123
- const messageId = firstTrimmedString(data.messageId, data.msgid, tracking?.messageId, tracking?.msgid);
121
+ const messageId = firstTrimmedString(
122
+ data.messageId,
123
+ data.msgid,
124
+ tracking?.messageId,
125
+ tracking?.msgid,
126
+ );
124
127
  const processQueryKey = firstTrimmedString(data.processQueryKey, tracking?.processQueryKey);
125
128
  const outTrackId = firstTrimmedString(data.outTrackId, tracking?.outTrackId);
126
129
  const cardInstanceId = firstTrimmedString(data.cardInstanceId, tracking?.cardInstanceId);
@@ -152,8 +155,8 @@ function persistOutboundMessageContext(params: {
152
155
  }
153
156
  params.log?.debug?.(
154
157
  `[DingTalk][QuotedRef][Persist] direction=outbound scope=${params.conversationId} ` +
155
- `messageType=${params.messageType || "(none)"} processQueryKey=${params.delivery.processQueryKey || "(none)"} ` +
156
- `messageId=${params.delivery.messageId || "(none)"} quotedRef=${params.quotedRef ? JSON.stringify(params.quotedRef) : "(none)"}`,
158
+ `messageType=${params.messageType || "(none)"} processQueryKey=${params.delivery.processQueryKey || "(none)"} ` +
159
+ `messageId=${params.delivery.messageId || "(none)"} quotedRef=${params.quotedRef ? JSON.stringify(params.quotedRef) : "(none)"}`,
157
160
  );
158
161
  upsertOutboundMessageContext({
159
162
  storePath: params.storePath,
@@ -182,7 +185,9 @@ function buildPersistedOutboundText(text: string, options: SendMessageOptions):
182
185
  return text;
183
186
  }
184
187
 
185
- function shouldRouteSessionMediaViaProactive(mediaType?: string | null): mediaType is "voice" | "video" | "file" {
188
+ function shouldRouteSessionMediaViaProactive(
189
+ mediaType?: string | null,
190
+ ): mediaType is "voice" | "video" | "file" {
186
191
  return mediaType === "voice" || mediaType === "video" || mediaType === "file";
187
192
  }
188
193
 
@@ -273,12 +278,9 @@ function summarizeSessionWebhookResponse(data: unknown): string {
273
278
  }
274
279
  const payload = data as Record<string, unknown>;
275
280
  const code = extractErrorCodeFromResponseData(payload) || "(none)";
276
- const message = firstTrimmedString(
277
- payload.message,
278
- payload.errmsg,
279
- payload.msg,
280
- payload.errorMessage,
281
- ) || "(none)";
281
+ const message =
282
+ firstTrimmedString(payload.message, payload.errmsg, payload.msg, payload.errorMessage) ||
283
+ "(none)";
282
284
  const success =
283
285
  typeof payload.success === "boolean"
284
286
  ? String(payload.success)
@@ -294,21 +296,15 @@ function summarizeSessionWebhookResponse(data: unknown): string {
294
296
  );
295
297
  }
296
298
 
297
- function ensureSessionWebhookBusinessSuccess(
298
- data: unknown,
299
- context: { msgtype: string },
300
- ): void {
299
+ function ensureSessionWebhookBusinessSuccess(data: unknown, context: { msgtype: string }): void {
301
300
  if (!data || typeof data !== "object") {
302
301
  return;
303
302
  }
304
303
  const payload = data as Record<string, unknown>;
305
304
  const code = extractErrorCodeFromResponseData(payload);
306
- const message = firstTrimmedString(
307
- payload.message,
308
- payload.errmsg,
309
- payload.msg,
310
- payload.errorMessage,
311
- ) || "unknown error";
305
+ const message =
306
+ firstTrimmedString(payload.message, payload.errmsg, payload.msg, payload.errorMessage) ||
307
+ "unknown error";
312
308
 
313
309
  const hasFailureSuccessFlag = payload.success === false || payload.result === false;
314
310
  const hasFailureCode = typeof code === "string" && code !== "" && code !== "0";
@@ -319,7 +315,9 @@ function ensureSessionWebhookBusinessSuccess(
319
315
  const reason = [
320
316
  code && code !== "0" ? `code=${code}` : "",
321
317
  message !== "unknown error" ? `message=${message}` : "",
322
- ].filter(Boolean).join(" ");
318
+ ]
319
+ .filter(Boolean)
320
+ .join(" ");
323
321
  throw new Error(`Session webhook ${context.msgtype} send failed${reason ? `: ${reason}` : ""}`);
324
322
  }
325
323
 
@@ -407,7 +405,11 @@ export async function sendProactiveTextOrMarkdown(
407
405
  config.convertMarkdownTables !== false
408
406
  ? convertMarkdownTablesToPlainText(textWithUploadedLocalImages)
409
407
  : textWithUploadedLocalImages;
410
- const { useMarkdown, title } = detectMarkdownAndExtractTitle(normalizedText, options, "OpenClaw 提醒");
408
+ const { useMarkdown, title } = detectMarkdownAndExtractTitle(
409
+ normalizedText,
410
+ options,
411
+ "OpenClaw 提醒",
412
+ );
411
413
 
412
414
  log?.debug?.(
413
415
  `[DingTalk] Sending proactive message to ${isGroup ? "group" : "user"} ${resolvedTarget} with title "${title}"${proactiveRiskTag}`,
@@ -519,8 +521,9 @@ export async function sendProactiveMedia(
519
521
  msgParam = JSON.stringify({ photoURL: mediaId });
520
522
  } else if (mediaType === "voice") {
521
523
  msgKey = "sampleAudio";
522
- const durationMs = uploadedDurationMs
523
- ?? await getVoiceDurationMs(mediaPath, mediaType, log, { preReadBuffer: buffer });
524
+ const durationMs =
525
+ uploadedDurationMs ??
526
+ (await getVoiceDurationMs(mediaPath, mediaType, log, { preReadBuffer: buffer }));
524
527
  msgParam = JSON.stringify({ mediaId, duration: String(durationMs) });
525
528
  } else {
526
529
  // sampleVideo requires picMediaId; fallback to sampleFile for broader compatibility.
@@ -614,7 +617,10 @@ export async function sendProactiveMedia(
614
617
  ).catch((fallbackErr: any) => ({ __fallbackError: fallbackErr }));
615
618
 
616
619
  if ((fallback as any)?.__fallbackError) {
617
- return { ok: false, error: `${err.message}; fallback failed: ${(fallback as any).__fallbackError?.message || "unknown"}` };
620
+ return {
621
+ ok: false,
622
+ error: `${err.message}; fallback failed: ${(fallback as any).__fallbackError?.message || "unknown"}`,
623
+ };
618
624
  }
619
625
 
620
626
  const fallbackDelivery = extractOutboundDeliveryMetadata(fallback);
@@ -632,7 +638,9 @@ export async function sendProactiveMedia(
632
638
  chatType: inferConversationChatType(options.conversationId || normalizedTarget),
633
639
  delivery: {
634
640
  ...fallbackDelivery,
635
- kind: isTrackingResult(fallback as ProactiveTextSendResult) ? "proactive-card" : "proactive-text",
641
+ kind: isTrackingResult(fallback as ProactiveTextSendResult)
642
+ ? "proactive-card"
643
+ : "proactive-text",
636
644
  },
637
645
  });
638
646
  return { ok: true, data: fallback, messageId: fallbackMessageId };
@@ -675,9 +683,9 @@ export async function sendMedia(
675
683
  if (options.conversationId) {
676
684
  // conversationId explicitly provided (parsed from sessionKey)
677
685
  activeRun = options.expectedCardOwnerId
678
- ? resolveCardRunByConversation(accountId, options.conversationId, {
686
+ ? (resolveCardRunByConversation(accountId, options.conversationId, {
679
687
  ownerUserId: options.expectedCardOwnerId,
680
- }) ?? resolveCardRunByConversation(accountId, options.conversationId, undefined)
688
+ }) ?? resolveCardRunByConversation(accountId, options.conversationId, undefined))
681
689
  : resolveCardRunByConversation(accountId, options.conversationId, undefined);
682
690
  } else if (options.expectedCardOwnerId) {
683
691
  // Fallback: when sessionKey=- causes conversationId to be undefined,
@@ -686,7 +694,7 @@ export async function sendMedia(
686
694
  if (activeRun) {
687
695
  log?.debug?.(
688
696
  `[DingTalk] Matched active card by owner-only lookup (conversationId unavailable) ` +
689
- `accountId=${accountId} ownerUserId=${options.expectedCardOwnerId} outTrackId=${activeRun.outTrackId}`,
697
+ `accountId=${accountId} ownerUserId=${options.expectedCardOwnerId} outTrackId=${activeRun.outTrackId}`,
690
698
  );
691
699
  }
692
700
  }
@@ -744,13 +752,16 @@ export async function sendBySession(
744
752
  `[DingTalk] Session webhook image will be delivered as markdown media reference mediaId=${uploadResult.mediaId}`,
745
753
  );
746
754
  } else {
747
- const mediaHint = options.mediaUrl || options.mediaPath || options.filePath || "(媒体发送失败)";
755
+ const mediaHint =
756
+ options.mediaUrl || options.mediaPath || options.filePath || "(媒体发送失败)";
748
757
  text = `${text}\n\n📎 媒体发送失败,兜底链接/路径:${mediaHint}`.trim();
749
758
  log?.warn?.("[DingTalk] Media upload failed, falling back to text description");
750
759
  }
751
760
  } else {
752
- const mediaHint = options.mediaUrl || options.mediaPath || options.filePath || "(媒体发送失败)";
753
- text = `${text}\n\n📎 当前会话无法直接发送 ${options.mediaType},兜底链接/路径:${mediaHint}`.trim();
761
+ const mediaHint =
762
+ options.mediaUrl || options.mediaPath || options.filePath || "(媒体发送失败)";
763
+ text =
764
+ `${text}\n\n📎 当前会话无法直接发送 ${options.mediaType},兜底链接/路径:${mediaHint}`.trim();
754
765
  log?.warn?.(
755
766
  `[DingTalk] Session webhook does not support native ${options.mediaType} replies; falling back to text description`,
756
767
  );
@@ -768,7 +779,11 @@ export async function sendBySession(
768
779
  config.convertMarkdownTables !== false
769
780
  ? convertMarkdownTablesToPlainText(textWithUploadedLocalImages)
770
781
  : textWithUploadedLocalImages;
771
- const { useMarkdown, title } = detectMarkdownAndExtractTitle(normalizedText, options, "Clawdbot 消息");
782
+ const { useMarkdown, title } = detectMarkdownAndExtractTitle(
783
+ normalizedText,
784
+ options,
785
+ "Clawdbot 消息",
786
+ );
772
787
  const chunks = splitMarkdownChunks(normalizedText, DINGTALK_TEXT_CHUNK_LIMIT);
773
788
 
774
789
  let lastResult: any = null;
@@ -779,7 +794,13 @@ export async function sendBySession(
779
794
  if (options.atUserId && idx === chunks.length - 1) {
780
795
  finalText = `${finalText} @${options.atUserId}`;
781
796
  }
782
- body = { msgtype: "markdown", markdown: { title: chunks.length > 1 ? `${title} (${idx + 1}/${chunks.length})` : title, text: finalText } };
797
+ body = {
798
+ msgtype: "markdown",
799
+ markdown: {
800
+ title: chunks.length > 1 ? `${title} (${idx + 1}/${chunks.length})` : title,
801
+ text: finalText,
802
+ },
803
+ };
783
804
  } else {
784
805
  body = { msgtype: "text", text: { content: chunk } };
785
806
  }
@@ -808,16 +829,30 @@ export async function sendMessage(
808
829
  config: DingTalkConfig,
809
830
  conversationId: string,
810
831
  text: string,
811
- options: SendMessageOptions & { sessionWebhook?: string; card?: AICardInstance; accountId?: string } = {},
812
- ): Promise<{ ok: boolean; error?: string; data?: AxiosResponse; messageId?: string; tracking?: DingTalkTrackingMetadata }> {
832
+ options: SendMessageOptions & {
833
+ sessionWebhook?: string;
834
+ card?: AICardInstance;
835
+ accountId?: string;
836
+ } = {},
837
+ ): Promise<{
838
+ ok: boolean;
839
+ error?: string;
840
+ data?: AxiosResponse;
841
+ messageId?: string;
842
+ tracking?: DingTalkTrackingMetadata;
843
+ }> {
813
844
  try {
814
845
  const messageType = config.messageType || "markdown";
815
846
  const log = options.log || getLogger();
816
847
 
817
- if (options.sessionWebhook && options.mediaPath && shouldRouteSessionMediaViaProactive(options.mediaType)) {
848
+ if (
849
+ options.sessionWebhook &&
850
+ options.mediaPath &&
851
+ shouldRouteSessionMediaViaProactive(options.mediaType)
852
+ ) {
818
853
  log?.debug?.(
819
854
  `[DingTalk] Session webhook does not support ${options.mediaType} replies reliably; ` +
820
- "using proactive media API instead",
855
+ "using proactive media API instead",
821
856
  );
822
857
  const proactiveMediaResult = await sendProactiveMedia(
823
858
  config,
@@ -829,7 +864,7 @@ export async function sendMessage(
829
864
  if (!proactiveMediaResult.ok) {
830
865
  log?.warn?.(
831
866
  `[DingTalk] Proactive ${options.mediaType} reply failed; falling back to session markdown: ` +
832
- (proactiveMediaResult.error || "unknown"),
867
+ (proactiveMediaResult.error || "unknown"),
833
868
  );
834
869
  const data = await sendBySession(config, options.sessionWebhook, text, options);
835
870
  const delivery = extractOutboundDeliveryMetadata(data);
@@ -1,7 +1,7 @@
1
+ import axios from "../shared/http-client";
2
+ import { retryWithBackoff } from "../shared/utils";
1
3
  import { resolveRuntimeConfig } from "./config";
2
- import axios from "./http-client";
3
4
  import type { DingTalkConfig, Logger, TokenInfo } from "./types";
4
- import { retryWithBackoff } from "./utils";
5
5
 
6
6
  interface TokenCache {
7
7
  accessToken: string;
@@ -1,6 +1,6 @@
1
- import { getAccessToken } from "../auth";
2
- import type { DingTalkChannelPlugin } from "../types";
3
- import { getCurrentTimestamp } from "../utils";
1
+ import { getCurrentTimestamp } from "../shared/utils";
2
+ import { getAccessToken } from "./auth";
3
+ import type { DingTalkChannelPlugin } from "./types";
4
4
 
5
5
  export function createDingTalkStatus(): NonNullable<DingTalkChannelPlugin["status"]> {
6
6
  return {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { DEFAULT_MESSAGE_CONTEXT_TTL_DAYS } from "./message-context-store";
2
+ import { DEFAULT_MESSAGE_CONTEXT_TTL_DAYS } from "../messaging/message-context-store";
3
3
  import { buildSecretInputSchema } from "./secret-input";
4
4
 
5
5
  const AckReactionSchema = z.union([
@@ -9,8 +9,51 @@ const AckReactionSchema = z.union([
9
9
  ]);
10
10
 
11
11
  const CardStreamingModeSchema = z.enum(["off", "answer", "all"]);
12
+ const CardTaskProgressRefreshSchema = z.enum(["heartbeat", "interval"]);
12
13
  const ContextVisibilitySchema = z.enum(["all", "allowlist", "allowlist_quote"]);
13
14
 
15
+ /**
16
+ * Gateway RPC capability gates (Issue #608, 问题 3).
17
+ * Shape aligned with the official connector's `tools: { docs, media }` gate;
18
+ * all capabilities default to enabled for backward compatibility.
19
+ * Allowlists must list at least one entry when present: an empty list would be
20
+ * ambiguous, so it is rejected at config-validation time instead of silently
21
+ * disabling the restriction.
22
+ */
23
+ const GatewayTargetListSchema = z
24
+ .array(z.string().regex(/^(user|group):\S+$/, "must be `user:<id>` or `group:<conversationId>`"))
25
+ .min(1)
26
+ .optional();
27
+
28
+ const DingTalkGatewayCapabilitiesSchema = z
29
+ .object({
30
+ tools: z
31
+ .object({
32
+ /** Enable dingtalk.docs.* and dingtalk-connector.docs.* Gateway RPCs (default: true) */
33
+ docs: z.boolean().optional(),
34
+ /** Enable dingtalk-connector.sendToUser/sendToGroup/send proactive-send RPCs (default: true) */
35
+ proactiveSend: z.boolean().optional(),
36
+ })
37
+ .strict()
38
+ .optional(),
39
+ docs: z
40
+ .object({
41
+ /** When set, docs RPCs only accept these spaceId values (at least one entry) */
42
+ allowedSpaceIds: z.array(z.string().min(1)).min(1).optional(),
43
+ })
44
+ .strict()
45
+ .optional(),
46
+ send: z
47
+ .object({
48
+ /** When set, proactive-send RPCs only accept these `user:*` / `group:*` targets (at least one entry) */
49
+ allowedTargets: GatewayTargetListSchema,
50
+ })
51
+ .strict()
52
+ .optional(),
53
+ })
54
+ .strict()
55
+ .optional();
56
+
14
57
  /**
15
58
  * Runtime-parsed DingTalk account config.
16
59
  *
@@ -121,7 +164,13 @@ const DingTalkAccountConfigShape = {
121
164
  /** Show the proactive-send permission hint when the runtime detects missing DingTalk proactive permission. */
122
165
  enabled: z.boolean().optional().default(true),
123
166
  /** Minimum cooldown in hours before the same proactive permission hint can be shown again. */
124
- cooldownHours: z.number().int().min(1).max(24 * 30).optional().default(24),
167
+ cooldownHours: z
168
+ .number()
169
+ .int()
170
+ .min(1)
171
+ .max(24 * 30)
172
+ .optional()
173
+ .default(24),
125
174
  })
126
175
  .optional()
127
176
  .default({ enabled: true, cooldownHours: 24 }),
@@ -138,8 +187,26 @@ const DingTalkAccountConfigShape = {
138
187
  /** Throttle interval in milliseconds between AI card streaming updates. */
139
188
  cardStreamInterval: z.number().int().min(200).optional().default(1000),
140
189
 
190
+ /** Live "task in progress" block on AI cards for long-running tasks.
191
+ * - true: always enabled
192
+ * - false: always disabled
193
+ * - unset (default): enabled, except when `cardStreamingMode` is explicitly `"off"` */
194
+ cardTaskProgress: z.boolean().optional(),
195
+
196
+ /** How tool-driven stage/step changes reach the progress block:
197
+ * - heartbeat (effective default): coalesced into the 30s refresh, so a task
198
+ * costs one update on appearance plus one per 30s
199
+ * - interval: pushed on every correlated tool event, throttled by
200
+ * `cardStreamInterval` (more updates, fresher stage label) */
201
+ cardTaskProgressRefresh: CardTaskProgressRefreshSchema.optional(),
202
+
141
203
  /** Cooldown window in milliseconds after AI card trigger errors. Replies fall back to non-card delivery during this period. */
142
- aicardDegradeMs: z.number().int().min(60_000).optional().default(30 * 60 * 1000),
204
+ aicardDegradeMs: z
205
+ .number()
206
+ .int()
207
+ .min(60_000)
208
+ .optional()
209
+ .default(30 * 60 * 1000),
143
210
 
144
211
  /** Enable the local feedback-learning loop for notes, reflections, and command-assisted learning. */
145
212
  learningEnabled: z.boolean().optional(),
@@ -175,7 +242,17 @@ const DingTalkAccountConfigShape = {
175
242
  dapiUsage: z.boolean().optional().default(false),
176
243
  })
177
244
  .optional()
178
- .default({ model: true, effort: true, agent: true, taskTime: false, tokens: false, dapiUsage: false }),
245
+ .default({
246
+ model: true,
247
+ effort: true,
248
+ agent: true,
249
+ taskTime: false,
250
+ tokens: false,
251
+ dapiUsage: false,
252
+ }),
253
+
254
+ /** Gateway RPC capability gates and allowlists (docs RPCs + proactive-send RPCs). Named `gatewayCapabilities` because host `OpenClawConfig` already owns `gateway` and `tools`, and a bare `capabilities` key would collide with this plugin's channel capabilities metadata. */
255
+ gatewayCapabilities: DingTalkGatewayCapabilitiesSchema,
179
256
  } as const;
180
257
 
181
258
  const DingTalkAccountConfigSchema = z.object(DingTalkAccountConfigShape);