@soimy/dingtalk 3.6.9 → 3.6.11

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 (182) hide show
  1. package/dist/index.d.ts +3 -12
  2. package/dist/index.js +1262 -320
  3. package/dist/src/access-control.d.ts +0 -1
  4. package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts +0 -1
  5. package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts +0 -1
  6. package/dist/src/ack-reaction/dynamic-ack-reaction-progress.d.ts +0 -1
  7. package/dist/src/ack-reaction-classifier.d.ts +0 -1
  8. package/dist/src/ack-reaction-service.d.ts +0 -1
  9. package/dist/src/auth.d.ts +0 -1
  10. package/dist/src/card/ask-user-question-context.d.ts +0 -1
  11. package/dist/src/card/ask-user-question-store.d.ts +0 -1
  12. package/dist/src/card/ask-user-question.d.ts +0 -1
  13. package/dist/src/card/card-action-handler.d.ts +0 -1
  14. package/dist/src/card/card-markdown-image-reroute.d.ts +0 -1
  15. package/dist/src/card/card-run-registry.d.ts +0 -1
  16. package/dist/src/card/card-stop-handler.d.ts +0 -1
  17. package/dist/src/card/card-streaming-mode.d.ts +0 -1
  18. package/dist/src/card/card-template.d.ts +0 -1
  19. package/dist/src/card/reasoning-answer-split.d.ts +0 -1
  20. package/dist/src/card/reasoning-block-assembler.d.ts +0 -1
  21. package/dist/src/card/statusline-renderer.d.ts +0 -1
  22. package/dist/src/card/task-model-metadata.d.ts +0 -1
  23. package/dist/src/card-callback-service.d.ts +0 -1
  24. package/dist/src/card-draft-controller.d.ts +0 -1
  25. package/dist/src/card-service.d.ts +0 -1
  26. package/dist/src/channel.d.ts +0 -1
  27. package/dist/src/command/card-stop-command.d.ts +0 -1
  28. package/dist/src/command/inbound-command-dispatch-service.d.ts +0 -1
  29. package/dist/src/config-schema.d.ts +0 -1
  30. package/dist/src/config.d.ts +0 -1
  31. package/dist/src/connection-manager.d.ts +0 -1
  32. package/dist/src/dedup.d.ts +0 -1
  33. package/dist/src/device-registration.d.ts +0 -1
  34. package/dist/src/docs-service.d.ts +4 -5
  35. package/dist/src/draft-stream-loop.d.ts +0 -1
  36. package/dist/src/feedback-learning-service.d.ts +0 -1
  37. package/dist/src/feedback-learning-store.d.ts +0 -1
  38. package/dist/src/gateway/channel-gateway.d.ts +0 -1
  39. package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts +0 -1
  40. package/dist/src/gateway/inbound-session-queue.d.ts +0 -1
  41. package/dist/src/gateway/reply-session-conflict.d.ts +0 -1
  42. package/dist/src/http-client.d.ts +0 -1
  43. package/dist/src/inbound-handler.d.ts +0 -1
  44. package/dist/src/learning-command-service.d.ts +0 -1
  45. package/dist/src/logger-context.d.ts +0 -1
  46. package/dist/src/media-utils.d.ts +0 -1
  47. package/dist/src/message-context-store.d.ts +0 -1
  48. package/dist/src/message-utils.d.ts +0 -1
  49. package/dist/src/messaging/attachment-text-extractor.d.ts +0 -1
  50. package/dist/src/messaging/btw-deliver.d.ts +0 -1
  51. package/dist/src/messaging/channel-actions.d.ts +0 -1
  52. package/dist/src/messaging/channel-outbound.d.ts +0 -1
  53. package/dist/src/messaging/inline-directives.d.ts +42 -0
  54. package/dist/src/messaging/quoted-context.d.ts +0 -1
  55. package/dist/src/messaging/quoted-file-service.d.ts +0 -1
  56. package/dist/src/messaging/quoted-ref.d.ts +0 -1
  57. package/dist/src/onboarding.d.ts +0 -1
  58. package/dist/src/path-utils.d.ts +0 -1
  59. package/dist/src/peer-id-registry.d.ts +0 -12
  60. package/dist/src/persistence-store.d.ts +0 -1
  61. package/dist/src/platform/channel-status.d.ts +0 -1
  62. package/dist/src/plugin-sdk-channel-actions-augment.d.ts +0 -1
  63. package/dist/src/proactive-risk-registry.d.ts +0 -1
  64. package/dist/src/reply-strategy-card.d.ts +0 -1
  65. package/dist/src/reply-strategy-markdown.d.ts +0 -1
  66. package/dist/src/reply-strategy-types.d.ts +0 -1
  67. package/dist/src/reply-strategy-with-reaction.d.ts +0 -1
  68. package/dist/src/reply-strategy.d.ts +0 -1
  69. package/dist/src/run-usage-store.d.ts +0 -1
  70. package/dist/src/runtime.d.ts +0 -1
  71. package/dist/src/secret-input.d.ts +3 -3
  72. package/dist/src/send-service.d.ts +0 -1
  73. package/dist/src/session-command-service.d.ts +0 -1
  74. package/dist/src/session-lock.d.ts +0 -1
  75. package/dist/src/session-peer-store.d.ts +0 -1
  76. package/dist/src/session-routing.d.ts +0 -1
  77. package/dist/src/session-state.d.ts +0 -1
  78. package/dist/src/signature.d.ts +0 -1
  79. package/dist/src/targeting/agent-name-matcher.d.ts +0 -1
  80. package/dist/src/targeting/agent-routing.d.ts +0 -1
  81. package/dist/src/targeting/group-members-store.d.ts +0 -1
  82. package/dist/src/targeting/target-directory-adapter.d.ts +0 -1
  83. package/dist/src/targeting/target-directory-store.d.ts +0 -1
  84. package/dist/src/targeting/target-input.d.ts +0 -1
  85. package/dist/src/types.d.ts +1 -2
  86. package/dist/src/utils.d.ts +0 -1
  87. package/index.ts +8 -2
  88. package/openclaw.plugin.json +4 -3
  89. package/package.json +10 -8
  90. package/src/channel.ts +4 -3
  91. package/src/gateway/channel-gateway.ts +67 -4
  92. package/src/inbound-handler.ts +6 -2
  93. package/src/messaging/channel-actions.ts +1 -4
  94. package/src/messaging/inline-directives.ts +342 -0
  95. package/src/peer-id-registry.ts +0 -100
  96. package/src/secret-input.ts +33 -33
  97. package/src/types.ts +1 -1
  98. package/dist/index.d.ts.map +0 -1
  99. package/dist/index.js.map +0 -7
  100. package/dist/src/access-control.d.ts.map +0 -1
  101. package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts.map +0 -1
  102. package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts.map +0 -1
  103. package/dist/src/ack-reaction/dynamic-ack-reaction-progress.d.ts.map +0 -1
  104. package/dist/src/ack-reaction-classifier.d.ts.map +0 -1
  105. package/dist/src/ack-reaction-service.d.ts.map +0 -1
  106. package/dist/src/auth.d.ts.map +0 -1
  107. package/dist/src/card/ask-user-question-context.d.ts.map +0 -1
  108. package/dist/src/card/ask-user-question-store.d.ts.map +0 -1
  109. package/dist/src/card/ask-user-question.d.ts.map +0 -1
  110. package/dist/src/card/card-action-handler.d.ts.map +0 -1
  111. package/dist/src/card/card-markdown-image-reroute.d.ts.map +0 -1
  112. package/dist/src/card/card-run-registry.d.ts.map +0 -1
  113. package/dist/src/card/card-stop-handler.d.ts.map +0 -1
  114. package/dist/src/card/card-streaming-mode.d.ts.map +0 -1
  115. package/dist/src/card/card-template.d.ts.map +0 -1
  116. package/dist/src/card/reasoning-answer-split.d.ts.map +0 -1
  117. package/dist/src/card/reasoning-block-assembler.d.ts.map +0 -1
  118. package/dist/src/card/statusline-renderer.d.ts.map +0 -1
  119. package/dist/src/card/task-model-metadata.d.ts.map +0 -1
  120. package/dist/src/card-callback-service.d.ts.map +0 -1
  121. package/dist/src/card-draft-controller.d.ts.map +0 -1
  122. package/dist/src/card-service.d.ts.map +0 -1
  123. package/dist/src/channel.d.ts.map +0 -1
  124. package/dist/src/command/card-stop-command.d.ts.map +0 -1
  125. package/dist/src/command/inbound-command-dispatch-service.d.ts.map +0 -1
  126. package/dist/src/config-schema.d.ts.map +0 -1
  127. package/dist/src/config.d.ts.map +0 -1
  128. package/dist/src/connection-manager.d.ts.map +0 -1
  129. package/dist/src/dedup.d.ts.map +0 -1
  130. package/dist/src/device-registration.d.ts.map +0 -1
  131. package/dist/src/docs-service.d.ts.map +0 -1
  132. package/dist/src/draft-stream-loop.d.ts.map +0 -1
  133. package/dist/src/feedback-learning-service.d.ts.map +0 -1
  134. package/dist/src/feedback-learning-store.d.ts.map +0 -1
  135. package/dist/src/gateway/channel-gateway.d.ts.map +0 -1
  136. package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts.map +0 -1
  137. package/dist/src/gateway/inbound-session-queue.d.ts.map +0 -1
  138. package/dist/src/gateway/reply-session-conflict.d.ts.map +0 -1
  139. package/dist/src/http-client.d.ts.map +0 -1
  140. package/dist/src/inbound-handler.d.ts.map +0 -1
  141. package/dist/src/learning-command-service.d.ts.map +0 -1
  142. package/dist/src/logger-context.d.ts.map +0 -1
  143. package/dist/src/media-utils.d.ts.map +0 -1
  144. package/dist/src/message-context-store.d.ts.map +0 -1
  145. package/dist/src/message-utils.d.ts.map +0 -1
  146. package/dist/src/messaging/attachment-text-extractor.d.ts.map +0 -1
  147. package/dist/src/messaging/btw-deliver.d.ts.map +0 -1
  148. package/dist/src/messaging/channel-actions.d.ts.map +0 -1
  149. package/dist/src/messaging/channel-outbound.d.ts.map +0 -1
  150. package/dist/src/messaging/quoted-context.d.ts.map +0 -1
  151. package/dist/src/messaging/quoted-file-service.d.ts.map +0 -1
  152. package/dist/src/messaging/quoted-ref.d.ts.map +0 -1
  153. package/dist/src/onboarding.d.ts.map +0 -1
  154. package/dist/src/path-utils.d.ts.map +0 -1
  155. package/dist/src/peer-id-registry.d.ts.map +0 -1
  156. package/dist/src/persistence-store.d.ts.map +0 -1
  157. package/dist/src/platform/channel-status.d.ts.map +0 -1
  158. package/dist/src/plugin-sdk-channel-actions-augment.d.ts.map +0 -1
  159. package/dist/src/proactive-risk-registry.d.ts.map +0 -1
  160. package/dist/src/reply-strategy-card.d.ts.map +0 -1
  161. package/dist/src/reply-strategy-markdown.d.ts.map +0 -1
  162. package/dist/src/reply-strategy-types.d.ts.map +0 -1
  163. package/dist/src/reply-strategy-with-reaction.d.ts.map +0 -1
  164. package/dist/src/reply-strategy.d.ts.map +0 -1
  165. package/dist/src/run-usage-store.d.ts.map +0 -1
  166. package/dist/src/runtime.d.ts.map +0 -1
  167. package/dist/src/secret-input.d.ts.map +0 -1
  168. package/dist/src/send-service.d.ts.map +0 -1
  169. package/dist/src/session-command-service.d.ts.map +0 -1
  170. package/dist/src/session-lock.d.ts.map +0 -1
  171. package/dist/src/session-peer-store.d.ts.map +0 -1
  172. package/dist/src/session-routing.d.ts.map +0 -1
  173. package/dist/src/session-state.d.ts.map +0 -1
  174. package/dist/src/signature.d.ts.map +0 -1
  175. package/dist/src/targeting/agent-name-matcher.d.ts.map +0 -1
  176. package/dist/src/targeting/agent-routing.d.ts.map +0 -1
  177. package/dist/src/targeting/group-members-store.d.ts.map +0 -1
  178. package/dist/src/targeting/target-directory-adapter.d.ts.map +0 -1
  179. package/dist/src/targeting/target-directory-store.d.ts.map +0 -1
  180. package/dist/src/targeting/target-input.d.ts.map +0 -1
  181. package/dist/src/types.d.ts.map +0 -1
  182. package/dist/src/utils.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,51 +1,24 @@
1
1
  // index.ts
2
- import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
2
+ import {
3
+ defineChannelPluginEntry
4
+ } from "openclaw/plugin-sdk/core";
3
5
  import { readStringParam as readStringParam2 } from "openclaw/plugin-sdk/param-readers";
4
6
 
5
7
  // src/secret-input.ts
6
- import { readFile } from "node:fs/promises";
8
+ import {
9
+ hasConfiguredSecretInput as hasConfiguredOpenClawSecretInput,
10
+ resolveConfiguredSecretInputString
11
+ } from "openclaw/plugin-sdk/secret-input-runtime";
7
12
  import { z } from "zod";
8
13
 
9
- // src/path-utils.ts
10
- import * as os from "node:os";
11
- import * as path from "node:path";
12
- var WINDOWS_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
13
- "Users",
14
- "Program Files",
15
- "Program Files (x86)",
16
- "ProgramData",
17
- "Windows",
18
- "Documents and Settings"
19
- ]);
20
- function resolveRelativePath(input) {
21
- const trimmed = input.trim();
22
- if (!trimmed) {
23
- return trimmed;
24
- }
25
- const segments = (value) => value.split(/[\\/]+/).filter(Boolean);
26
- const pathSegments = segments(trimmed);
27
- const firstSegment = pathSegments[0];
28
- if (trimmed === "~") {
29
- return path.resolve(os.homedir());
30
- }
31
- if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
32
- return path.resolve(os.homedir(), ...segments(trimmed.slice(2)));
33
- }
34
- if (process.platform === "win32") {
35
- if (/^[a-zA-Z]:[\\/]/.test(trimmed)) {
36
- return path.win32.normalize(trimmed);
37
- }
38
- if (firstSegment && /^[a-zA-Z]:$/.test(firstSegment)) {
39
- return path.win32.resolve(`${firstSegment}\\`, ...pathSegments.slice(1));
40
- }
41
- if (firstSegment && WINDOWS_ROOT_DIRECTORIES.has(firstSegment)) {
42
- return path.win32.resolve("\\", ...pathSegments);
43
- }
44
- }
45
- if (/^[\\/]/.test(trimmed)) {
46
- return path.resolve(path.sep, ...pathSegments);
47
- }
48
- return path.resolve(process.cwd(), ...pathSegments);
14
+ // src/runtime.ts
15
+ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
16
+ var runtimeStore = createPluginRuntimeStore("DingTalk runtime not initialized");
17
+ function setDingTalkRuntime(next) {
18
+ runtimeStore.setRuntime(next);
19
+ }
20
+ function getDingTalkRuntime() {
21
+ return runtimeStore.getRuntime();
49
22
  }
50
23
 
51
24
  // src/secret-input.ts
@@ -62,16 +35,6 @@ function buildSecretInputFailure(value, reason) {
62
35
  function formatSecretInputResolutionFailure(failure) {
63
36
  return `${failure.source}:${failure.provider}:${failure.id} - ${failure.reason}`;
64
37
  }
65
- function buildSecretInputSchema() {
66
- return z.union([
67
- z.string(),
68
- z.object({
69
- source: z.enum(["env", "file"]),
70
- provider: z.string().min(1).max(1024).regex(SECRET_INPUT_PROVIDER_PATTERN),
71
- id: z.string().min(1).max(1024).regex(SECRET_INPUT_ID_PATTERN)
72
- })
73
- ]);
74
- }
75
38
  function isSecretInputRef(value) {
76
39
  if (!value || typeof value !== "object") {
77
40
  return false;
@@ -80,16 +43,7 @@ function isSecretInputRef(value) {
80
43
  return (ref.source === "env" || ref.source === "file") && typeof ref.provider === "string" && ref.provider.trim().length > 0 && SECRET_INPUT_PROVIDER_PATTERN.test(ref.provider) && typeof ref.id === "string" && ref.id.trim().length > 0 && SECRET_INPUT_ID_PATTERN.test(ref.id);
81
44
  }
82
45
  function hasConfiguredSecretInput(value) {
83
- if (typeof value === "string") {
84
- return value.trim().length > 0;
85
- }
86
- if (!isSecretInputRef(value)) {
87
- return false;
88
- }
89
- if (value.source === "env") {
90
- return Boolean(process.env[value.id]?.trim());
91
- }
92
- return true;
46
+ return hasConfiguredOpenClawSecretInput(value);
93
47
  }
94
48
  function normalizeSecretInputString(value) {
95
49
  if (typeof value === "string") {
@@ -119,7 +73,7 @@ function parseSecretInputString(value) {
119
73
  id: match[3]
120
74
  };
121
75
  }
122
- async function resolveSecretInputStringWithFailure(value, log) {
76
+ async function resolveSecretInputStringWithFailure(value, log, hostConfig) {
123
77
  if (typeof value === "string") {
124
78
  const trimmed = value.trim();
125
79
  return { value: trimmed || void 0 };
@@ -127,32 +81,40 @@ async function resolveSecretInputStringWithFailure(value, log) {
127
81
  if (!isSecretInputRef(value)) {
128
82
  return {};
129
83
  }
130
- if (value.source === "env") {
131
- const envValue = process.env[value.id]?.trim();
132
- if (envValue) {
133
- return { value: envValue };
84
+ if (!hostConfig) {
85
+ try {
86
+ hostConfig = getDingTalkRuntime().config.current();
87
+ } catch {
88
+ hostConfig = {};
134
89
  }
135
- const failure = buildSecretInputFailure(value, "environment variable is not set or is empty");
136
- log?.warn?.("[DingTalk][SecretInput] Failed to resolve env secret", {
90
+ }
91
+ try {
92
+ const resolved = await resolveConfiguredSecretInputString({
93
+ config: hostConfig,
94
+ env: process.env,
95
+ value,
96
+ path: "channels.dingtalk.clientSecret",
97
+ unresolvedReasonStyle: "detailed"
98
+ });
99
+ if (resolved.value) {
100
+ return { value: resolved.value };
101
+ }
102
+ const failure = buildSecretInputFailure(
103
+ value,
104
+ resolved.unresolvedRefReason || "secret reference is unresolved"
105
+ );
106
+ log?.warn?.("[DingTalk][SecretInput] Failed to resolve secret", {
137
107
  provider: value.provider,
138
108
  id: value.id,
139
109
  error: failure.reason
140
110
  });
141
111
  return { failure };
142
- }
143
- try {
144
- const filePath = resolveRelativePath(value.id);
145
- const secret = (await readFile(filePath, "utf8")).trim();
146
- if (secret) {
147
- return { value: secret };
148
- }
149
- return { failure: buildSecretInputFailure(value, "file secret is empty") };
150
112
  } catch (error) {
151
113
  const failure = buildSecretInputFailure(
152
114
  value,
153
115
  error instanceof Error ? error.message : String(error)
154
116
  );
155
- log?.warn?.("[DingTalk][SecretInput] Failed to read file secret", {
117
+ log?.warn?.("[DingTalk][SecretInput] Failed to resolve secret", {
156
118
  provider: value.provider,
157
119
  id: value.id,
158
120
  error: failure.reason
@@ -169,6 +131,48 @@ async function resolveDingTalkSecretConfig(config, log) {
169
131
  };
170
132
  }
171
133
 
134
+ // src/path-utils.ts
135
+ import * as os from "node:os";
136
+ import * as path from "node:path";
137
+ var WINDOWS_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
138
+ "Users",
139
+ "Program Files",
140
+ "Program Files (x86)",
141
+ "ProgramData",
142
+ "Windows",
143
+ "Documents and Settings"
144
+ ]);
145
+ function resolveRelativePath(input) {
146
+ const trimmed = input.trim();
147
+ if (!trimmed) {
148
+ return trimmed;
149
+ }
150
+ const segments = (value) => value.split(/[\\/]+/).filter(Boolean);
151
+ const pathSegments = segments(trimmed);
152
+ const firstSegment = pathSegments[0];
153
+ if (trimmed === "~") {
154
+ return path.resolve(os.homedir());
155
+ }
156
+ if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
157
+ return path.resolve(os.homedir(), ...segments(trimmed.slice(2)));
158
+ }
159
+ if (process.platform === "win32") {
160
+ if (/^[a-zA-Z]:[\\/]/.test(trimmed)) {
161
+ return path.win32.normalize(trimmed);
162
+ }
163
+ if (firstSegment && /^[a-zA-Z]:$/.test(firstSegment)) {
164
+ return path.win32.resolve(`${firstSegment}\\`, ...pathSegments.slice(1));
165
+ }
166
+ if (firstSegment && WINDOWS_ROOT_DIRECTORIES.has(firstSegment)) {
167
+ return path.win32.resolve("\\", ...pathSegments);
168
+ }
169
+ }
170
+ if (/^[\\/]/.test(trimmed)) {
171
+ return path.resolve(path.sep, ...pathSegments);
172
+ }
173
+ return path.resolve(process.cwd(), ...pathSegments);
174
+ }
175
+
172
176
  // src/config.ts
173
177
  var DEFAULT_LEARNING_NOTE_TTL_MS = 6 * 60 * 60 * 1e3;
174
178
  function normalizeLearningConfig(config, options) {
@@ -1031,8 +1035,238 @@ async function updateCardVariables(outTrackId, params, token, config) {
1031
1035
  // src/inbound-handler.ts
1032
1036
  import fs6 from "node:fs";
1033
1037
  import * as path10 from "node:path";
1038
+ import { formatInboundEnvelope } from "openclaw/plugin-sdk/channel-inbound";
1034
1039
  import { isAbortRequestText, isBtwRequestText } from "openclaw/plugin-sdk/reply-runtime";
1035
- import { parseInlineDirectives } from "openclaw/plugin-sdk/text-runtime";
1040
+
1041
+ // src/messaging/inline-directives.ts
1042
+ import { findCodeRegions, isInsideCode } from "openclaw/plugin-sdk/text-chunking";
1043
+ var AUDIO_TAG_RE = /\[\[\s*audio_as_voice\s*\]\]/gi;
1044
+ var REPLY_TAG_RE = /\[\[\s*(?:reply_to_current|reply_to\s*:\s*([^\]\n]+))\s*\]\]/gi;
1045
+ var MAX_REPLY_DIRECTIVE_ID_LENGTH = 256;
1046
+ var BLOCK_SENTINEL_SEED = "\uE000";
1047
+ var INDENTED_CODE_LINE_RE = /(?:^|\n)((?: {4}|\t)[^\n]*)(?:\n(?: {4}|\t)[^\n]*)*/g;
1048
+ function normalizeOptionalString(value) {
1049
+ if (typeof value !== "string") {
1050
+ return void 0;
1051
+ }
1052
+ const trimmed = value.trim();
1053
+ return trimmed ? trimmed : void 0;
1054
+ }
1055
+ function resolveCodeRegions(text) {
1056
+ const indentedRegions = [];
1057
+ const indentRe = new RegExp(INDENTED_CODE_LINE_RE.source, "g");
1058
+ let match;
1059
+ while ((match = indentRe.exec(text)) !== null) {
1060
+ const start = match.index + (match[0].charCodeAt(0) === 10 ? 1 : 0);
1061
+ indentedRegions.push({ start, end: match.index + match[0].length });
1062
+ }
1063
+ const sdkRegions = findCodeRegions(text).filter(
1064
+ (region) => (text[region.start] !== "`" || !isEscaped(text, region.start)) && !indentedRegions.some(
1065
+ (indented) => region.start >= indented.start && region.start < indented.end
1066
+ )
1067
+ );
1068
+ const knownRegions = mergeCodeRegions([...sdkRegions, ...indentedRegions]);
1069
+ return mergeCodeRegions([...knownRegions, ...findDelimiterCodeRegions(text, knownRegions)]);
1070
+ }
1071
+ function isEscaped(text, offset) {
1072
+ let slashes = 0;
1073
+ for (let index = offset - 1; text[index] === "\\"; index--) {
1074
+ slashes++;
1075
+ }
1076
+ return slashes % 2 === 1;
1077
+ }
1078
+ function isStrictlyInsideCode(offset, regions) {
1079
+ return regions.some((region) => offset > region.start && offset < region.end);
1080
+ }
1081
+ function findDelimiterCodeRegions(text, knownRegions) {
1082
+ const regions = [];
1083
+ let match;
1084
+ const fences = /^( {0,3})(`{3,}|~{3,})[^\n]*(?:\n|$)/gm;
1085
+ let open;
1086
+ while ((match = fences.exec(text)) !== null) {
1087
+ const delimiter = match[2];
1088
+ if (!open) {
1089
+ const suffix = match[0].slice(match[1].length + delimiter.length).trim();
1090
+ if (isStrictlyInsideCode(match.index, knownRegions) || delimiter[0] === "`" && suffix.includes("`")) {
1091
+ continue;
1092
+ }
1093
+ open = { char: delimiter[0], length: delimiter.length, start: match.index };
1094
+ } else if (delimiter[0] === open.char && delimiter.length >= open.length && match[0].slice(match[1].length + delimiter.length).trim() === "") {
1095
+ regions.push({ start: open.start, end: fences.lastIndex });
1096
+ open = void 0;
1097
+ }
1098
+ }
1099
+ if (open) {
1100
+ regions.push({ start: open.start, end: text.length });
1101
+ }
1102
+ const fencedRegions = [...regions];
1103
+ const pendingTicks = /* @__PURE__ */ new Map();
1104
+ const ticks = /`+/g;
1105
+ while ((match = ticks.exec(text)) !== null) {
1106
+ const length = match[0].length;
1107
+ if (isEscaped(text, match.index) || isInsideCode(match.index, fencedRegions) || pendingTicks.get(length) === void 0 && isStrictlyInsideCode(match.index, knownRegions)) {
1108
+ continue;
1109
+ }
1110
+ const start = pendingTicks.get(length);
1111
+ if (start === void 0) {
1112
+ pendingTicks.set(length, match.index);
1113
+ continue;
1114
+ }
1115
+ regions.push({ start, end: ticks.lastIndex });
1116
+ for (const [pendingLength, pendingStart] of pendingTicks) {
1117
+ if (pendingStart >= start) {
1118
+ pendingTicks.delete(pendingLength);
1119
+ }
1120
+ }
1121
+ }
1122
+ return regions;
1123
+ }
1124
+ function mergeCodeRegions(regions) {
1125
+ if (regions.length <= 1) {
1126
+ return regions;
1127
+ }
1128
+ const sorted = [...regions].toSorted((a, b) => a.start - b.start || a.end - b.end);
1129
+ const merged = [];
1130
+ for (const region of sorted) {
1131
+ const last = merged[merged.length - 1];
1132
+ if (last && region.start <= last.end) {
1133
+ last.end = Math.max(last.end, region.end);
1134
+ } else {
1135
+ merged.push({ start: region.start, end: region.end });
1136
+ }
1137
+ }
1138
+ return merged;
1139
+ }
1140
+ function createBlockSentinel(text) {
1141
+ let sentinel = BLOCK_SENTINEL_SEED;
1142
+ while (text.includes(sentinel)) {
1143
+ sentinel += BLOCK_SENTINEL_SEED;
1144
+ }
1145
+ return sentinel;
1146
+ }
1147
+ function normalizeDirectiveWhitespace(text, codeRegions = []) {
1148
+ const blockSentinel = createBlockSentinel(text);
1149
+ const blockPlaceholderRe = new RegExp(`${blockSentinel}(\\d+)${blockSentinel}`, "g");
1150
+ const blocks = [];
1151
+ let masked = "";
1152
+ let cursor = 0;
1153
+ for (const region of codeRegions) {
1154
+ if (region.start < cursor) {
1155
+ continue;
1156
+ }
1157
+ blocks.push(text.slice(region.start, region.end));
1158
+ masked += `${text.slice(cursor, region.start)}${blockSentinel}${blocks.length - 1}${blockSentinel}`;
1159
+ cursor = region.end;
1160
+ }
1161
+ masked += text.slice(cursor);
1162
+ return masked.replace(/\r\n/g, "\n").replace(/([^\s])[ \t]{2,}([^\s])/g, "$1 $2").replace(/^\n+/, "").replace(/^[ \t](?=\S)/, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trimEnd().replace(blockPlaceholderRe, (_full, index) => blocks[Number(index)] ?? "");
1163
+ }
1164
+ function replacementPreservesWordBoundary(source, offset, length) {
1165
+ const before = source[offset - 1];
1166
+ const after = source[offset + length];
1167
+ return before && after && !/\s/u.test(before) && !/\s/u.test(after) ? " " : "";
1168
+ }
1169
+ function stripUnsafeReplyDirectiveChars(value) {
1170
+ const chars = [];
1171
+ for (const ch of value) {
1172
+ const code = ch.charCodeAt(0);
1173
+ if (code >= 0 && code <= 31 || code === 127 || code >= 128 && code <= 159 || ch === "[" || ch === "]") {
1174
+ continue;
1175
+ }
1176
+ chars.push(ch);
1177
+ }
1178
+ return chars.join("");
1179
+ }
1180
+ function sanitizeReplyDirectiveId(rawReplyToId) {
1181
+ const trimmed = rawReplyToId?.trim();
1182
+ if (!trimmed) {
1183
+ return void 0;
1184
+ }
1185
+ const sanitized = stripUnsafeReplyDirectiveChars(trimmed).trim();
1186
+ if (!sanitized) {
1187
+ return void 0;
1188
+ }
1189
+ const chars = Array.from(sanitized);
1190
+ if (chars.length > MAX_REPLY_DIRECTIVE_ID_LENGTH) {
1191
+ return chars.slice(0, MAX_REPLY_DIRECTIVE_ID_LENGTH).join("");
1192
+ }
1193
+ return sanitized;
1194
+ }
1195
+ function parseInlineDirectives(text, options = {}) {
1196
+ const { currentMessageId, stripAudioTag = true, stripReplyTags = true } = options;
1197
+ if (!text) {
1198
+ return {
1199
+ text: "",
1200
+ audioAsVoice: false,
1201
+ replyToCurrent: false,
1202
+ hasAudioTag: false,
1203
+ hasReplyTag: false
1204
+ };
1205
+ }
1206
+ if (!text.includes("[[")) {
1207
+ return {
1208
+ text: normalizeDirectiveWhitespace(text, resolveCodeRegions(text)),
1209
+ audioAsVoice: false,
1210
+ replyToCurrent: false,
1211
+ hasAudioTag: false,
1212
+ hasReplyTag: false
1213
+ };
1214
+ }
1215
+ const codeRegions = resolveCodeRegions(text);
1216
+ let cleaned = text;
1217
+ let audioAsVoice = false;
1218
+ let hasAudioTag = false;
1219
+ let hasReplyTag = false;
1220
+ let sawCurrent = false;
1221
+ let lastExplicitId;
1222
+ cleaned = cleaned.replace(AUDIO_TAG_RE, (match, offset, source) => {
1223
+ if (isInsideCode(offset, codeRegions)) {
1224
+ return match;
1225
+ }
1226
+ audioAsVoice = true;
1227
+ hasAudioTag = true;
1228
+ return stripAudioTag ? replacementPreservesWordBoundary(source, offset, match.length) : match;
1229
+ });
1230
+ const codeRegionsAfterAudioStrip = resolveCodeRegions(cleaned);
1231
+ cleaned = cleaned.replace(
1232
+ REPLY_TAG_RE,
1233
+ (match, idRaw, offset, source) => {
1234
+ if (isInsideCode(offset, codeRegionsAfterAudioStrip)) {
1235
+ return match;
1236
+ }
1237
+ hasReplyTag = true;
1238
+ if (idRaw === void 0) {
1239
+ sawCurrent = true;
1240
+ } else {
1241
+ const id = sanitizeReplyDirectiveId(idRaw);
1242
+ if (id) {
1243
+ lastExplicitId = id;
1244
+ }
1245
+ }
1246
+ return stripReplyTags ? replacementPreservesWordBoundary(source, offset, match.length) : match;
1247
+ }
1248
+ );
1249
+ if (!hasAudioTag && !hasReplyTag) {
1250
+ return {
1251
+ text,
1252
+ audioAsVoice: false,
1253
+ replyToCurrent: false,
1254
+ hasAudioTag: false,
1255
+ hasReplyTag: false
1256
+ };
1257
+ }
1258
+ cleaned = normalizeDirectiveWhitespace(cleaned, resolveCodeRegions(cleaned));
1259
+ const replyToId = lastExplicitId ?? (sawCurrent ? normalizeOptionalString(currentMessageId) : void 0);
1260
+ return {
1261
+ text: cleaned,
1262
+ audioAsVoice,
1263
+ replyToId,
1264
+ replyToExplicitId: lastExplicitId,
1265
+ replyToCurrent: sawCurrent,
1266
+ hasAudioTag,
1267
+ hasReplyTag
1268
+ };
1269
+ }
1036
1270
 
1037
1271
  // src/access-control.ts
1038
1272
  function normalizeAllowFrom(list) {
@@ -1679,110 +1913,20 @@ var DINGTALK_ASK_USER_CARD_TEMPLATE = Object.freeze({
1679
1913
  templateId: BUILTIN_DINGTALK_ASK_USER_CARD_TEMPLATE_ID
1680
1914
  });
1681
1915
 
1682
- // src/peer-id-registry.ts
1683
- import { readFileSync, readdirSync as readdirSync2 } from "fs";
1684
- import * as os3 from "os";
1685
- import { join as join2 } from "path";
1686
-
1687
- // src/logger-context.ts
1688
- var currentLogger;
1689
- var loggerByAccountId = /* @__PURE__ */ new Map();
1690
- function setCurrentLogger(log, accountId) {
1691
- currentLogger = log;
1692
- const normalizedAccountId = typeof accountId === "string" ? accountId.trim() : "";
1693
- if (!normalizedAccountId) {
1694
- return;
1695
- }
1696
- if (log) {
1697
- loggerByAccountId.set(normalizedAccountId, log);
1698
- return;
1699
- }
1700
- loggerByAccountId.delete(normalizedAccountId);
1701
- }
1702
- function getLogger(accountId) {
1703
- const normalizedAccountId = typeof accountId === "string" ? accountId.trim() : "";
1704
- if (normalizedAccountId) {
1705
- return loggerByAccountId.get(normalizedAccountId);
1706
- }
1707
- return currentLogger;
1708
- }
1709
-
1710
1916
  // src/peer-id-registry.ts
1711
1917
  var peerIdMap = /* @__PURE__ */ new Map();
1712
- var preloaded = false;
1713
1918
  function registerPeerId(originalId) {
1714
1919
  if (!originalId) {
1715
1920
  return;
1716
1921
  }
1717
1922
  peerIdMap.set(originalId.toLowerCase(), originalId);
1718
1923
  }
1719
- function maybeRegisterDingTalkGroupPeerId(value) {
1720
- if (typeof value === "string" && value.startsWith("cid")) {
1721
- registerPeerId(value);
1722
- }
1723
- }
1724
1924
  function resolveOriginalPeerId(id) {
1725
1925
  if (!id) {
1726
1926
  return id;
1727
1927
  }
1728
- if (!preloaded) {
1729
- preloadPeerIdsFromSessions();
1730
- }
1731
1928
  return peerIdMap.get(id.toLowerCase()) || id;
1732
1929
  }
1733
- function preloadPeerIdsFromSessions(homeDir) {
1734
- if (!homeDir && preloaded) {
1735
- return;
1736
- }
1737
- const home = homeDir || os3.homedir();
1738
- const agentsDir = join2(home, ".openclaw", "agents");
1739
- const log = getLogger();
1740
- let preloadCompleted = false;
1741
- try {
1742
- const agentDirs = readdirSync2(agentsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1743
- for (const agentName of agentDirs) {
1744
- const sessionsPath = join2(agentsDir, agentName, "sessions", "sessions.json");
1745
- try {
1746
- const raw = readFileSync(sessionsPath, "utf-8");
1747
- const parsed = JSON.parse(raw);
1748
- if (!parsed || typeof parsed !== "object") {
1749
- continue;
1750
- }
1751
- for (const session of Object.values(parsed)) {
1752
- if (!session || typeof session !== "object") {
1753
- continue;
1754
- }
1755
- const sessionRecord = session;
1756
- maybeRegisterDingTalkGroupPeerId(sessionRecord.lastTo);
1757
- const origin = sessionRecord.origin;
1758
- if (!origin || typeof origin !== "object") {
1759
- continue;
1760
- }
1761
- const originRecord = origin;
1762
- maybeRegisterDingTalkGroupPeerId(originRecord.from);
1763
- maybeRegisterDingTalkGroupPeerId(originRecord.to);
1764
- }
1765
- } catch (err) {
1766
- log?.debug?.(
1767
- `[DingTalk][PeerIdRegistry] Failed to parse preload sessions file ${sessionsPath}: ${err instanceof Error ? err.message : String(err)}`
1768
- );
1769
- }
1770
- }
1771
- preloadCompleted = true;
1772
- } catch (err) {
1773
- const errorCode = err?.code;
1774
- if (errorCode === "ENOENT") {
1775
- preloadCompleted = true;
1776
- } else {
1777
- log?.debug?.(
1778
- `[DingTalk][PeerIdRegistry] Failed to scan preload directory ${agentsDir}: ${err instanceof Error ? err.message : String(err)}`
1779
- );
1780
- }
1781
- }
1782
- if (!homeDir && preloadCompleted) {
1783
- preloaded = true;
1784
- }
1785
- }
1786
1930
 
1787
1931
  // src/persistence-store.ts
1788
1932
  import * as fs2 from "node:fs";
@@ -3639,18 +3783,6 @@ function resolveConfiguredTaskModelMetadata(params) {
3639
3783
 
3640
3784
  // src/command/card-stop-command.ts
3641
3785
  import { resolveNativeCommandSessionTargets } from "openclaw/plugin-sdk/command-auth";
3642
-
3643
- // src/runtime.ts
3644
- import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
3645
- var runtimeStore = createPluginRuntimeStore("DingTalk runtime not initialized");
3646
- function setDingTalkRuntime(next) {
3647
- runtimeStore.setRuntime(next);
3648
- }
3649
- function getDingTalkRuntime() {
3650
- return runtimeStore.getRuntime();
3651
- }
3652
-
3653
- // src/command/card-stop-command.ts
3654
3786
  async function dispatchDingTalkCardStopCommand(params) {
3655
3787
  const rt = getDingTalkRuntime();
3656
3788
  const { sessionKey: commandSessionKey, commandTargetSessionKey } = resolveNativeCommandSessionTargets({
@@ -5161,9 +5293,32 @@ function pickQueueBusyAckPhrase(seed) {
5161
5293
  // src/send-service.ts
5162
5294
  import * as path6 from "node:path";
5163
5295
 
5296
+ // src/logger-context.ts
5297
+ var currentLogger;
5298
+ var loggerByAccountId = /* @__PURE__ */ new Map();
5299
+ function setCurrentLogger(log, accountId) {
5300
+ currentLogger = log;
5301
+ const normalizedAccountId = typeof accountId === "string" ? accountId.trim() : "";
5302
+ if (!normalizedAccountId) {
5303
+ return;
5304
+ }
5305
+ if (log) {
5306
+ loggerByAccountId.set(normalizedAccountId, log);
5307
+ return;
5308
+ }
5309
+ loggerByAccountId.delete(normalizedAccountId);
5310
+ }
5311
+ function getLogger(accountId) {
5312
+ const normalizedAccountId = typeof accountId === "string" ? accountId.trim() : "";
5313
+ if (normalizedAccountId) {
5314
+ return loggerByAccountId.get(normalizedAccountId);
5315
+ }
5316
+ return currentLogger;
5317
+ }
5318
+
5164
5319
  // src/media-utils.ts
5165
5320
  import { randomUUID as randomUUID3 } from "node:crypto";
5166
- import * as os4 from "node:os";
5321
+ import * as os3 from "node:os";
5167
5322
  import * as path5 from "node:path";
5168
5323
  import { promises as fsPromises } from "node:fs";
5169
5324
  import { lookup as dnsLookup } from "node:dns/promises";
@@ -5319,7 +5474,7 @@ async function prepareVoiceUploadPath(mediaPath, log) {
5319
5474
  if (DINGTALK_VOICE_UPLOAD_EXTENSIONS.has(ext)) {
5320
5475
  return { path: mediaPath };
5321
5476
  }
5322
- const outputPath = path5.join(os4.tmpdir(), `dingtalk_voice_${randomUUID3()}.ogg`);
5477
+ const outputPath = path5.join(os3.tmpdir(), `dingtalk_voice_${randomUUID3()}.ogg`);
5323
5478
  await runFfmpeg([
5324
5479
  "-y",
5325
5480
  "-i",
@@ -5684,7 +5839,7 @@ async function prepareMediaInput(input, log, mediaUrlAllowlist) {
5684
5839
  const contentType = typeof response.headers?.["content-type"] === "string" ? response.headers["content-type"] : void 0;
5685
5840
  const urlPath = currentUrl.pathname;
5686
5841
  const ext = path5.extname(urlPath) || detectExtensionFromContentType(contentType) || ".bin";
5687
- const tempPath = path5.join(os4.tmpdir(), `dingtalk_${randomUUID3()}${ext}`);
5842
+ const tempPath = path5.join(os3.tmpdir(), `dingtalk_${randomUUID3()}${ext}`);
5688
5843
  const buffer = Buffer.isBuffer(response.data) ? response.data : Buffer.from(response.data);
5689
5844
  await fsPromises.writeFile(tempPath, buffer);
5690
5845
  log?.debug?.(`[DingTalk] Downloaded remote media to temp file: ${tempPath}`);
@@ -12116,7 +12271,7 @@ ${attachmentExtractedText}` : inboundBody;
12116
12271
  }
12117
12272
  const groupMembers = !isDirect ? formatGroupMembers(storePath, groupId) : void 0;
12118
12273
  const fromLabel = isDirect ? `${senderName} (${senderId})` : `${groupName} - ${senderName}`;
12119
- const body = rt.channel.reply.formatInboundEnvelope({
12274
+ const body = formatInboundEnvelope({
12120
12275
  channel: "DingTalk",
12121
12276
  from: fromLabel,
12122
12277
  timestamp: data.createAt,
@@ -13826,139 +13981,869 @@ function registerDingTalkAskUserQuestionTool(api) {
13826
13981
  }
13827
13982
 
13828
13983
  // src/channel.ts
13829
- import { buildChannelConfigSchema } from "openclaw/plugin-sdk/core";
13984
+ import { buildJsonChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
13830
13985
 
13831
- // src/config-schema.ts
13832
- import { z as z2 } from "zod";
13833
- var AckReactionSchema = z2.union([
13834
- z2.literal(""),
13835
- z2.enum(["off", "emoji", "kaomoji"]),
13836
- z2.string().min(1)
13837
- ]);
13838
- var CardStreamingModeSchema = z2.enum(["off", "answer", "all"]);
13839
- var ContextVisibilitySchema = z2.enum(["all", "allowlist", "allowlist_quote"]);
13840
- var DingTalkAccountConfigShape = {
13841
- /** Account name (optional display name) */
13842
- name: z2.string().optional(),
13843
- /** Enable or disable this DingTalk channel/account without deleting saved credentials. */
13844
- enabled: z2.boolean().optional().default(true),
13845
- /** DingTalk App Key (Client ID) used to authenticate API and Stream connections. */
13846
- clientId: z2.string().optional(),
13847
- /** DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. */
13848
- clientSecret: buildSecretInputSchema().optional(),
13849
- /** Direct-message access policy: open, pairing, or allowlist. */
13850
- dmPolicy: z2.enum(["open", "pairing", "allowlist"]).optional().default("open"),
13851
- /** Group-message access policy: open, allowlist, or disabled. */
13852
- groupPolicy: z2.enum(["open", "allowlist", "disabled"]).optional().default("open"),
13853
- /** User IDs allowed when `dmPolicy` is `allowlist`. */
13854
- allowFrom: z2.array(z2.string()).optional(),
13855
- /** Sender IDs allowed when `groupPolicy` is `allowlist`. */
13856
- groupAllowFrom: z2.array(z2.string()).optional(),
13857
- /** Default disabled. Enabling `all` allows learned displayName lookup but may misroute on stale or duplicate names and is available to all callers until upstream exposes requester authz context. */
13858
- displayNameResolution: z2.enum(["disabled", "all"]).optional().default("disabled"),
13859
- /** Controls how much supplemental host context remains visible to the reply runtime. `allowlist_quote` is the safest advanced mode when only explicit quotes or replies should remain visible. */
13860
- contextVisibility: ContextVisibilitySchema.optional(),
13861
- /** Allowed remote media download hosts, IPs, or CIDRs for media fetches. */
13862
- mediaUrlAllowlist: z2.array(z2.string()).optional(),
13863
- /** Native acknowledgement reaction mode: off, emoji, kaomoji, or a custom compatibility string. */
13864
- ackReaction: AckReactionSchema.optional(),
13865
- /** Retention window in days for short-lived message context used by quoting and media recovery. */
13866
- journalTTLDays: z2.number().int().min(1).optional().default(DEFAULT_MESSAGE_CONTEXT_TTL_DAYS),
13867
- /** Enable verbose DingTalk channel debug logging. */
13868
- debug: z2.boolean().optional().default(false),
13869
- /** Default reply delivery mode: markdown or card. */
13870
- messageType: z2.enum(["markdown", "card"]).optional().default("markdown"),
13871
- /** Deprecated and ignored. AI card replies now always use the built-in DingTalk template contract. Keep only for backward-compatible config parsing. */
13872
- cardTemplateId: z2.string().optional(),
13873
- /** Deprecated and ignored. The built-in AI card contract owns the streaming field mapping. Keep only for backward-compatible config parsing. */
13874
- cardTemplateKey: z2.string().optional().default("content"),
13875
- /** Per-group overrides keyed by conversationId. Supports `*` as a wildcard fallback. */
13876
- groups: z2.record(
13877
- z2.string(),
13878
- z2.object({
13879
- /** Additional system prompt appended for this group. */
13880
- systemPrompt: z2.string().optional(),
13881
- /** Require an explicit @mention before the bot answers in this group. */
13882
- requireMention: z2.boolean().optional(),
13883
- /** Optional per-group sender allowlist for tighter access control than the channel default. */
13884
- groupAllowFrom: z2.array(z2.string()).optional()
13885
- })
13886
- ).optional(),
13887
- /** Connection robustness configuration */
13888
- /** Maximum connection attempts in a single reconnect cycle before backing off or giving up. */
13889
- maxConnectionAttempts: z2.number().int().min(1).optional().default(10),
13890
- /** Initial reconnect backoff delay in milliseconds. */
13891
- initialReconnectDelay: z2.number().int().min(100).optional().default(1e3),
13892
- /** Upper bound for reconnect backoff delay in milliseconds. */
13893
- maxReconnectDelay: z2.number().int().min(1e3).optional().default(6e4),
13894
- /** Randomization factor added to reconnect backoff to avoid synchronized reconnect storms. */
13895
- reconnectJitter: z2.number().min(0).max(1).optional().default(0.3),
13896
- /** Maximum reconnect cycles before the channel stops retrying and waits for the next lifecycle restart. */
13897
- maxReconnectCycles: z2.number().int().min(1).optional().default(10),
13898
- /** Time limit in milliseconds for one reconnect cycle before starting a fresh cycle. */
13899
- reconnectDeadlineMs: z2.number().int().min(5e3).optional().default(5e4),
13900
- /** Enable the plugin connection manager. Disable only when you intentionally rely on DWClient native keepAlive plus autoReconnect behavior. */
13901
- useConnectionManager: z2.boolean().optional().default(true),
13902
- /** Maximum inbound media size in MB accepted by the plugin. When omitted, the runtime default is used. */
13903
- mediaMaxMb: z2.number().int().min(1).optional(),
13904
- /** Enable the underlying Stream client heartbeat. When omitted, runtime derives a default from `useConnectionManager`. */
13905
- keepAlive: z2.boolean().optional(),
13906
- /** Bypass global or system HTTP(S) proxy settings for DingTalk send, upload, and card APIs. */
13907
- bypassProxyForSend: z2.boolean().optional().default(false),
13908
- /** Controls the proactive-send permission reminder shown when a conversation has not granted send rights yet. */
13909
- proactivePermissionHint: z2.object({
13910
- /** Show the proactive-send permission hint when the runtime detects missing DingTalk proactive permission. */
13911
- enabled: z2.boolean().optional().default(true),
13912
- /** Minimum cooldown in hours before the same proactive permission hint can be shown again. */
13913
- cooldownHours: z2.number().int().min(1).max(24 * 30).optional().default(24)
13914
- }).optional().default({ enabled: true, cooldownHours: 24 }),
13915
- /** Deprecated compatibility flag. When true and `cardStreamingMode` is unset, runtime resolves to `cardStreamingMode: "all"`. Do not use in new configs. */
13916
- cardRealTimeStream: z2.boolean().optional(),
13917
- /** Card streaming mode:
13918
- * - off: disable incremental streaming
13919
- * - answer: stream answer text
13920
- * - all: stream answer + reasoning or thinking text */
13921
- cardStreamingMode: CardStreamingModeSchema.optional(),
13922
- /** Throttle interval in milliseconds between AI card streaming updates. */
13923
- cardStreamInterval: z2.number().int().min(200).optional().default(1e3),
13924
- /** Cooldown window in milliseconds after AI card trigger errors. Replies fall back to non-card delivery during this period. */
13925
- aicardDegradeMs: z2.number().int().min(6e4).optional().default(30 * 60 * 1e3),
13926
- /** Enable the local feedback-learning loop for notes, reflections, and command-assisted learning. */
13927
- learningEnabled: z2.boolean().optional(),
13928
- /** Automatically apply generated learning output into session notes or global rules when available. */
13929
- learningAutoApply: z2.boolean().optional(),
13930
- /** Retention window in milliseconds for temporary learning notes. */
13931
- learningNoteTtlMs: z2.number().int().min(6e4).optional(),
13932
- /** Convert markdown tables to plain text before sending when you want more consistent DingTalk rendering. */
13933
- convertMarkdownTables: z2.boolean().optional().default(true),
13934
- /** @mention the sender after card finalization in group chats.
13935
- * Set to a non-empty string (e.g. "✅ 回复完成") to enable the value is used as the message text.
13936
- * Leave empty or omit to disable. */
13937
- cardAtSender: z2.string().optional(),
13938
- /** Status line visibility toggles for the AI card footer. */
13939
- cardStatusLine: z2.object({
13940
- /** Show model name. */
13941
- model: z2.boolean().optional().default(true),
13942
- /** Show thinking effort level. */
13943
- effort: z2.boolean().optional().default(true),
13944
- /** Show agent display name. */
13945
- agent: z2.boolean().optional().default(true),
13946
- /** Show task elapsed time. */
13947
- taskTime: z2.boolean().optional().default(false),
13948
- /** Show token usage summary (input/output/cache). */
13949
- tokens: z2.boolean().optional().default(false),
13950
- /** Show DingTalk API call count. */
13951
- dapiUsage: z2.boolean().optional().default(false)
13952
- }).optional().default({ model: true, effort: true, agent: true, taskTime: false, tokens: false, dapiUsage: false })
13986
+ // openclaw.plugin.json
13987
+ var openclaw_plugin_default = {
13988
+ id: "dingtalk",
13989
+ name: "DingTalk",
13990
+ channels: ["dingtalk"],
13991
+ contracts: {
13992
+ tools: ["dingtalk_ask_user_question"]
13993
+ },
13994
+ configSchema: {
13995
+ type: "object",
13996
+ additionalProperties: true,
13997
+ properties: {}
13998
+ },
13999
+ channelConfigs: {
14000
+ dingtalk: {
14001
+ label: "DingTalk",
14002
+ description: "\u9489\u9489\u4F01\u4E1A\u5185\u90E8\u673A\u5668\u4EBA\uFF0C\u4F7F\u7528 Stream \u6A21\u5F0F\uFF0C\u65E0\u9700\u516C\u7F51 IP\u3002",
14003
+ schema: {
14004
+ type: "object",
14005
+ additionalProperties: false,
14006
+ properties: {
14007
+ name: {
14008
+ type: "string",
14009
+ description: "Optional display name shown for this DingTalk channel or default account."
14010
+ },
14011
+ enabled: {
14012
+ type: "boolean",
14013
+ default: true,
14014
+ description: "Enable or disable this DingTalk channel configuration without deleting saved credentials."
14015
+ },
14016
+ agentId: {
14017
+ type: "integer",
14018
+ description: "Legacy compatibility field from older setup flows. The current Stream-mode runtime no longer reads this value, so new configs should omit it."
14019
+ },
14020
+ corpId: {
14021
+ type: "string",
14022
+ description: "Legacy compatibility field from older enterprise app setup flows. The current Stream-mode runtime no longer needs or reads this value."
14023
+ },
14024
+ clientId: {
14025
+ type: "string",
14026
+ description: "DingTalk App Key (Client ID) used to authenticate API and Stream connections."
14027
+ },
14028
+ clientSecret: {
14029
+ anyOf: [
14030
+ { type: "string" },
14031
+ {
14032
+ type: "object",
14033
+ additionalProperties: false,
14034
+ required: ["source", "provider", "id"],
14035
+ properties: {
14036
+ source: { type: "string", enum: ["env", "file"] },
14037
+ provider: {
14038
+ type: "string",
14039
+ minLength: 1,
14040
+ maxLength: 1024,
14041
+ pattern: "^[^:>]+$"
14042
+ },
14043
+ id: {
14044
+ type: "string",
14045
+ minLength: 1,
14046
+ maxLength: 1024,
14047
+ pattern: "^[^>]+$"
14048
+ }
14049
+ }
14050
+ }
14051
+ ],
14052
+ description: "DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. Supports env/file SecretInput references resolved by configured OpenClaw secret providers."
14053
+ },
14054
+ dmPolicy: {
14055
+ type: "string",
14056
+ enum: ["open", "pairing", "allowlist"],
14057
+ default: "open",
14058
+ description: "Direct-message access policy. open allows any DM, pairing requires host pairing, and allowlist only permits IDs listed in allowFrom."
14059
+ },
14060
+ groupPolicy: {
14061
+ type: "string",
14062
+ enum: ["open", "allowlist", "disabled"],
14063
+ default: "open",
14064
+ description: "Group-message access policy. open allows all groups, allowlist restricts by groupAllowFrom, and disabled blocks group handling."
14065
+ },
14066
+ allowFrom: {
14067
+ type: "array",
14068
+ items: { type: "string" },
14069
+ description: "User IDs allowed when dmPolicy is allowlist. Accepts DingTalk IDs or host-normalized IDs."
14070
+ },
14071
+ groupAllowFrom: {
14072
+ type: "array",
14073
+ items: { type: "string" },
14074
+ description: "Sender IDs allowed when groupPolicy is allowlist. Leave empty unless you want per-sender restrictions in groups."
14075
+ },
14076
+ displayNameResolution: {
14077
+ type: "string",
14078
+ enum: ["disabled", "all"],
14079
+ default: "disabled",
14080
+ description: "Whether learned display names can be used to resolve DingTalk users or groups. Keep disabled unless you explicitly accept stale or ambiguous-name routing risk."
14081
+ },
14082
+ contextVisibility: {
14083
+ type: "string",
14084
+ enum: ["all", "allowlist", "allowlist_quote"],
14085
+ description: "Host-level context filtering before prompts are sent to the runtime. allowlist_quote is the safest advanced mode when only explicit quote or reply context should remain visible."
14086
+ },
14087
+ mediaUrlAllowlist: {
14088
+ type: "array",
14089
+ items: { type: "string" },
14090
+ description: "Allowed download hosts, IPs, or CIDRs for remote media fetches. Use this for internal file servers or custom CDN domains."
14091
+ },
14092
+ ackReaction: {
14093
+ anyOf: [
14094
+ { type: "string", const: "" },
14095
+ { type: "string", enum: ["off", "emoji", "kaomoji"] },
14096
+ { type: "string", minLength: 1 }
14097
+ ],
14098
+ description: "Acknowledgement reaction mode. Supports off, emoji, kaomoji, or a custom compatibility string."
14099
+ },
14100
+ journalTTLDays: {
14101
+ type: "integer",
14102
+ minimum: 1,
14103
+ default: 7,
14104
+ description: "Retention window in days for short-lived message context used by quoting, media recovery, and related reply context features."
14105
+ },
14106
+ debug: {
14107
+ type: "boolean",
14108
+ default: false,
14109
+ description: "Enable verbose DingTalk channel debug logging."
14110
+ },
14111
+ messageType: {
14112
+ type: "string",
14113
+ enum: ["markdown", "card"],
14114
+ default: "markdown",
14115
+ description: "Default reply delivery mode. markdown uses standard messages; card uses DingTalk AI cards when available."
14116
+ },
14117
+ cardTemplateId: {
14118
+ type: "string",
14119
+ description: "Deprecated and ignored. AI card replies now always use the built-in DingTalk template contract; keep only for compatibility with old configs."
14120
+ },
14121
+ cardTemplateKey: {
14122
+ type: "string",
14123
+ default: "content",
14124
+ description: "Deprecated and ignored. The built-in AI card contract owns the streaming field mapping; keep only for compatibility with old configs."
14125
+ },
14126
+ groups: {
14127
+ type: "object",
14128
+ description: 'Per-group overrides keyed by conversationId. Supports "*" as a wildcard fallback.',
14129
+ additionalProperties: {
14130
+ type: "object",
14131
+ additionalProperties: false,
14132
+ properties: {
14133
+ systemPrompt: {
14134
+ type: "string",
14135
+ description: "Additional system prompt appended for this group."
14136
+ },
14137
+ requireMention: {
14138
+ type: "boolean",
14139
+ description: "Require an explicit @mention before the bot answers in this group."
14140
+ },
14141
+ groupAllowFrom: {
14142
+ type: "array",
14143
+ items: { type: "string" },
14144
+ description: "Per-group sender allowlist used when you want tighter access control than the channel default."
14145
+ }
14146
+ }
14147
+ }
14148
+ },
14149
+ maxConnectionAttempts: {
14150
+ type: "integer",
14151
+ minimum: 1,
14152
+ default: 10,
14153
+ description: "Maximum connection attempts in a single reconnect cycle before backing off or giving up."
14154
+ },
14155
+ initialReconnectDelay: {
14156
+ type: "integer",
14157
+ minimum: 100,
14158
+ default: 1e3,
14159
+ description: "Initial reconnect backoff delay in milliseconds."
14160
+ },
14161
+ maxReconnectDelay: {
14162
+ type: "integer",
14163
+ minimum: 1e3,
14164
+ default: 6e4,
14165
+ description: "Upper bound for reconnect backoff delay in milliseconds."
14166
+ },
14167
+ reconnectJitter: {
14168
+ type: "number",
14169
+ minimum: 0,
14170
+ maximum: 1,
14171
+ default: 0.3,
14172
+ description: "Randomization factor added to reconnect backoff to avoid synchronized reconnect storms."
14173
+ },
14174
+ maxReconnectCycles: {
14175
+ type: "integer",
14176
+ minimum: 1,
14177
+ default: 10,
14178
+ description: "Maximum reconnect cycles before the channel stops retrying and waits for the next lifecycle restart."
14179
+ },
14180
+ reconnectDeadlineMs: {
14181
+ type: "integer",
14182
+ minimum: 5e3,
14183
+ default: 5e4,
14184
+ description: "Time limit in milliseconds for one reconnect cycle before starting a fresh cycle."
14185
+ },
14186
+ useConnectionManager: {
14187
+ type: "boolean",
14188
+ default: true,
14189
+ description: "Enable the plugin connection manager. Disable only when you intentionally rely on the DingTalk client keepAlive and autoReconnect behavior."
14190
+ },
14191
+ mediaMaxMb: {
14192
+ type: "integer",
14193
+ minimum: 1,
14194
+ description: "Maximum inbound media size in MB accepted by the plugin. When omitted, the runtime default is used."
14195
+ },
14196
+ keepAlive: {
14197
+ type: "boolean",
14198
+ description: "Enable the underlying Stream client heartbeat. When omitted, runtime derives a default from useConnectionManager."
14199
+ },
14200
+ bypassProxyForSend: {
14201
+ type: "boolean",
14202
+ default: false,
14203
+ description: "Bypass global or system HTTP(S) proxy settings for DingTalk send, upload, and card APIs."
14204
+ },
14205
+ proactivePermissionHint: {
14206
+ type: "object",
14207
+ description: "Controls the proactive-send permission reminder shown when a conversation has not granted send rights yet.",
14208
+ additionalProperties: false,
14209
+ properties: {
14210
+ enabled: {
14211
+ type: "boolean",
14212
+ default: true,
14213
+ description: "Show the proactive-send permission hint when the runtime detects missing DingTalk proactive permission."
14214
+ },
14215
+ cooldownHours: {
14216
+ type: "integer",
14217
+ minimum: 1,
14218
+ maximum: 720,
14219
+ default: 24,
14220
+ description: "Minimum cooldown in hours before the same proactive permission hint can be shown again."
14221
+ }
14222
+ }
14223
+ },
14224
+ cardRealTimeStream: {
14225
+ type: "boolean",
14226
+ default: false,
14227
+ description: 'Deprecated compatibility flag. If true and cardStreamingMode is unset, runtime falls back to cardStreamingMode="all". Do not use in new configs.'
14228
+ },
14229
+ cardStreamingMode: {
14230
+ type: "string",
14231
+ enum: ["off", "answer", "all"],
14232
+ description: "AI card incremental streaming mode. off disables streaming, answer streams answer text only, and all streams answer plus reasoning or thinking text."
14233
+ },
14234
+ cardStreamInterval: {
14235
+ type: "integer",
14236
+ minimum: 200,
14237
+ default: 1e3,
14238
+ description: "Throttle interval in milliseconds between AI card streaming updates."
14239
+ },
14240
+ aicardDegradeMs: {
14241
+ type: "integer",
14242
+ minimum: 6e4,
14243
+ default: 18e5,
14244
+ description: "Cooldown window in milliseconds after AI card trigger errors. During this window replies fall back to non-card delivery."
14245
+ },
14246
+ learningEnabled: {
14247
+ type: "boolean",
14248
+ description: "Enable the local feedback-learning loop for notes, reflections, and command-assisted learning."
14249
+ },
14250
+ learningAutoApply: {
14251
+ type: "boolean",
14252
+ description: "Automatically apply generated learning output into session notes or global rules when available."
14253
+ },
14254
+ learningNoteTtlMs: {
14255
+ type: "integer",
14256
+ minimum: 6e4,
14257
+ description: "Retention window in milliseconds for temporary learning notes."
14258
+ },
14259
+ convertMarkdownTables: {
14260
+ type: "boolean",
14261
+ default: true,
14262
+ description: "Convert markdown tables to plain text before sending when you want more consistent DingTalk rendering."
14263
+ },
14264
+ cardAtSender: {
14265
+ type: "string",
14266
+ description: "Send a follow-up @mention text after card finalization in group chats. Any non-empty string becomes the message body."
14267
+ },
14268
+ cardStatusLine: {
14269
+ type: "object",
14270
+ description: "Status line visibility toggles for the AI card footer.",
14271
+ additionalProperties: false,
14272
+ default: { model: true, effort: true, agent: true, taskTime: false, tokens: false, dapiUsage: false },
14273
+ properties: {
14274
+ model: {
14275
+ type: "boolean",
14276
+ default: true,
14277
+ description: "Show model name in AI card status line footer."
14278
+ },
14279
+ effort: {
14280
+ type: "boolean",
14281
+ default: true,
14282
+ description: "Show thinking effort level in AI card status line footer."
14283
+ },
14284
+ agent: {
14285
+ type: "boolean",
14286
+ default: true,
14287
+ description: "Show agent display name in AI card status line footer."
14288
+ },
14289
+ taskTime: {
14290
+ type: "boolean",
14291
+ default: false,
14292
+ description: "Show task elapsed time in AI card status line footer."
14293
+ },
14294
+ tokens: {
14295
+ type: "boolean",
14296
+ default: false,
14297
+ description: "Show token usage summary (input/output/cache) in AI card status line footer."
14298
+ },
14299
+ dapiUsage: {
14300
+ type: "boolean",
14301
+ default: false,
14302
+ description: "Show DingTalk API call count in AI card status line footer."
14303
+ }
14304
+ }
14305
+ },
14306
+ showThinkingStream: {
14307
+ type: "boolean",
14308
+ description: "Legacy compatibility field from older card streaming behavior. It is not used by the current runtime and new configs should omit it."
14309
+ },
14310
+ asyncMode: {
14311
+ type: "boolean",
14312
+ description: "Legacy compatibility field from older host or runtime integrations. It is not used by the current runtime and new configs should omit it."
14313
+ },
14314
+ accounts: {
14315
+ type: "object",
14316
+ description: "Named multi-account DingTalk configurations. Each account can override most top-level settings and inherits unspecified values from the top level.",
14317
+ additionalProperties: {
14318
+ type: "object",
14319
+ additionalProperties: false,
14320
+ properties: {
14321
+ name: {
14322
+ type: "string",
14323
+ description: "Optional display name shown for this named DingTalk account."
14324
+ },
14325
+ enabled: {
14326
+ type: "boolean",
14327
+ default: true,
14328
+ description: "Enable or disable this DingTalk account without deleting saved credentials."
14329
+ },
14330
+ clientId: {
14331
+ type: "string",
14332
+ description: "DingTalk App Key (Client ID) used to authenticate API and Stream connections."
14333
+ },
14334
+ clientSecret: {
14335
+ anyOf: [
14336
+ { type: "string" },
14337
+ {
14338
+ type: "object",
14339
+ additionalProperties: false,
14340
+ required: ["source", "provider", "id"],
14341
+ properties: {
14342
+ source: { type: "string", enum: ["env", "file"] },
14343
+ provider: {
14344
+ type: "string",
14345
+ minLength: 1,
14346
+ maxLength: 1024,
14347
+ pattern: "^[^:>]+$"
14348
+ },
14349
+ id: {
14350
+ type: "string",
14351
+ minLength: 1,
14352
+ maxLength: 1024,
14353
+ pattern: "^[^>]+$"
14354
+ }
14355
+ }
14356
+ }
14357
+ ],
14358
+ description: "DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. Supports env/file SecretInput references resolved by configured OpenClaw secret providers."
14359
+ },
14360
+ dmPolicy: {
14361
+ type: "string",
14362
+ enum: ["open", "pairing", "allowlist"],
14363
+ default: "open",
14364
+ description: "Direct-message access policy. open allows any DM, pairing requires host pairing, and allowlist only permits IDs listed in allowFrom."
14365
+ },
14366
+ groupPolicy: {
14367
+ type: "string",
14368
+ enum: ["open", "allowlist", "disabled"],
14369
+ default: "open",
14370
+ description: "Group-message access policy. open allows all groups, allowlist restricts by groupAllowFrom, and disabled blocks group handling."
14371
+ },
14372
+ allowFrom: {
14373
+ type: "array",
14374
+ items: { type: "string" },
14375
+ description: "User IDs allowed when dmPolicy is allowlist. Accepts DingTalk IDs or host-normalized IDs."
14376
+ },
14377
+ groupAllowFrom: {
14378
+ type: "array",
14379
+ items: { type: "string" },
14380
+ description: "Sender IDs allowed when groupPolicy is allowlist. Leave empty unless you want per-sender restrictions in groups."
14381
+ },
14382
+ displayNameResolution: {
14383
+ type: "string",
14384
+ enum: ["disabled", "all"],
14385
+ default: "disabled",
14386
+ description: "Whether learned display names can be used to resolve DingTalk users or groups. Keep disabled unless you explicitly accept stale or ambiguous-name routing risk."
14387
+ },
14388
+ contextVisibility: {
14389
+ type: "string",
14390
+ enum: ["all", "allowlist", "allowlist_quote"],
14391
+ description: "Host-level context filtering before prompts are sent to the runtime. allowlist_quote is the safest advanced mode when only explicit quote or reply context should remain visible."
14392
+ },
14393
+ mediaUrlAllowlist: {
14394
+ type: "array",
14395
+ items: { type: "string" },
14396
+ description: "Allowed download hosts, IPs, or CIDRs for remote media fetches. Use this for internal file servers or custom CDN domains."
14397
+ },
14398
+ ackReaction: {
14399
+ anyOf: [
14400
+ { type: "string", const: "" },
14401
+ { type: "string", enum: ["off", "emoji", "kaomoji"] },
14402
+ { type: "string", minLength: 1 }
14403
+ ],
14404
+ description: "Acknowledgement reaction mode. Supports off, emoji, kaomoji, or a custom compatibility string."
14405
+ },
14406
+ journalTTLDays: {
14407
+ type: "integer",
14408
+ minimum: 1,
14409
+ default: 7,
14410
+ description: "Retention window in days for short-lived message context used by quoting, media recovery, and related reply context features."
14411
+ },
14412
+ debug: {
14413
+ type: "boolean",
14414
+ default: false,
14415
+ description: "Enable verbose DingTalk channel debug logging."
14416
+ },
14417
+ messageType: {
14418
+ type: "string",
14419
+ enum: ["markdown", "card"],
14420
+ default: "markdown",
14421
+ description: "Default reply delivery mode. markdown uses standard messages; card uses DingTalk AI cards when available."
14422
+ },
14423
+ cardTemplateId: {
14424
+ type: "string",
14425
+ description: "Deprecated and ignored. AI card replies now always use the built-in DingTalk template contract; keep only for compatibility with old configs."
14426
+ },
14427
+ cardTemplateKey: {
14428
+ type: "string",
14429
+ default: "content",
14430
+ description: "Deprecated and ignored. The built-in AI card contract owns the streaming field mapping; keep only for compatibility with old configs."
14431
+ },
14432
+ groups: {
14433
+ type: "object",
14434
+ description: 'Per-group overrides keyed by conversationId. Supports "*" as a wildcard fallback.',
14435
+ additionalProperties: {
14436
+ type: "object",
14437
+ additionalProperties: false,
14438
+ properties: {
14439
+ systemPrompt: {
14440
+ type: "string",
14441
+ description: "Additional system prompt appended for this group."
14442
+ },
14443
+ requireMention: {
14444
+ type: "boolean",
14445
+ description: "Require an explicit @mention before the bot answers in this group."
14446
+ },
14447
+ groupAllowFrom: {
14448
+ type: "array",
14449
+ items: { type: "string" },
14450
+ description: "Per-group sender allowlist used when you want tighter access control than the channel default."
14451
+ }
14452
+ }
14453
+ }
14454
+ },
14455
+ maxConnectionAttempts: {
14456
+ type: "integer",
14457
+ minimum: 1,
14458
+ default: 10,
14459
+ description: "Maximum connection attempts in a single reconnect cycle before backing off or giving up."
14460
+ },
14461
+ initialReconnectDelay: {
14462
+ type: "integer",
14463
+ minimum: 100,
14464
+ default: 1e3,
14465
+ description: "Initial reconnect backoff delay in milliseconds."
14466
+ },
14467
+ maxReconnectDelay: {
14468
+ type: "integer",
14469
+ minimum: 1e3,
14470
+ default: 6e4,
14471
+ description: "Upper bound for reconnect backoff delay in milliseconds."
14472
+ },
14473
+ reconnectJitter: {
14474
+ type: "number",
14475
+ minimum: 0,
14476
+ maximum: 1,
14477
+ default: 0.3,
14478
+ description: "Randomization factor added to reconnect backoff to avoid synchronized reconnect storms."
14479
+ },
14480
+ maxReconnectCycles: {
14481
+ type: "integer",
14482
+ minimum: 1,
14483
+ default: 10,
14484
+ description: "Maximum reconnect cycles before the channel stops retrying and waits for the next lifecycle restart."
14485
+ },
14486
+ reconnectDeadlineMs: {
14487
+ type: "integer",
14488
+ minimum: 5e3,
14489
+ default: 5e4,
14490
+ description: "Time limit in milliseconds for one reconnect cycle before starting a fresh cycle."
14491
+ },
14492
+ useConnectionManager: {
14493
+ type: "boolean",
14494
+ default: true,
14495
+ description: "Enable the plugin connection manager. Disable only when you intentionally rely on the DingTalk client keepAlive and autoReconnect behavior."
14496
+ },
14497
+ mediaMaxMb: {
14498
+ type: "integer",
14499
+ minimum: 1,
14500
+ description: "Maximum inbound media size in MB accepted by the plugin. When omitted, the runtime default is used."
14501
+ },
14502
+ keepAlive: {
14503
+ type: "boolean",
14504
+ description: "Enable the underlying Stream client heartbeat. When omitted, runtime derives a default from useConnectionManager."
14505
+ },
14506
+ bypassProxyForSend: {
14507
+ type: "boolean",
14508
+ default: false,
14509
+ description: "Bypass global or system HTTP(S) proxy settings for DingTalk send, upload, and card APIs."
14510
+ },
14511
+ proactivePermissionHint: {
14512
+ type: "object",
14513
+ description: "Controls the proactive-send permission reminder shown when a conversation has not granted send rights yet.",
14514
+ additionalProperties: false,
14515
+ properties: {
14516
+ enabled: {
14517
+ type: "boolean",
14518
+ default: true,
14519
+ description: "Show the proactive-send permission hint when the runtime detects missing DingTalk proactive permission."
14520
+ },
14521
+ cooldownHours: {
14522
+ type: "integer",
14523
+ minimum: 1,
14524
+ maximum: 720,
14525
+ default: 24,
14526
+ description: "Minimum cooldown in hours before the same proactive permission hint can be shown again."
14527
+ }
14528
+ }
14529
+ },
14530
+ cardRealTimeStream: {
14531
+ type: "boolean",
14532
+ default: false,
14533
+ description: 'Deprecated compatibility flag. If true and cardStreamingMode is unset, runtime falls back to cardStreamingMode="all". Do not use in new configs.'
14534
+ },
14535
+ cardStreamingMode: {
14536
+ type: "string",
14537
+ enum: ["off", "answer", "all"],
14538
+ description: "AI card incremental streaming mode. off disables streaming, answer streams answer text only, and all streams answer plus reasoning or thinking text."
14539
+ },
14540
+ cardStreamInterval: {
14541
+ type: "integer",
14542
+ minimum: 200,
14543
+ default: 1e3,
14544
+ description: "Throttle interval in milliseconds between AI card streaming updates."
14545
+ },
14546
+ aicardDegradeMs: {
14547
+ type: "integer",
14548
+ minimum: 6e4,
14549
+ default: 18e5,
14550
+ description: "Cooldown window in milliseconds after AI card trigger errors. During this window replies fall back to non-card delivery."
14551
+ },
14552
+ learningEnabled: {
14553
+ type: "boolean",
14554
+ description: "Enable the local feedback-learning loop for notes, reflections, and command-assisted learning."
14555
+ },
14556
+ learningAutoApply: {
14557
+ type: "boolean",
14558
+ description: "Automatically apply generated learning output into session notes or global rules when available."
14559
+ },
14560
+ learningNoteTtlMs: {
14561
+ type: "integer",
14562
+ minimum: 6e4,
14563
+ description: "Retention window in milliseconds for temporary learning notes."
14564
+ },
14565
+ convertMarkdownTables: {
14566
+ type: "boolean",
14567
+ default: true,
14568
+ description: "Convert markdown tables to plain text before sending when you want more consistent DingTalk rendering."
14569
+ },
14570
+ cardAtSender: {
14571
+ type: "string",
14572
+ description: "Send a follow-up @mention text after card finalization in group chats. Any non-empty string becomes the message body."
14573
+ },
14574
+ cardStatusLine: {
14575
+ type: "object",
14576
+ description: "Status line visibility toggles for the AI card footer.",
14577
+ additionalProperties: false,
14578
+ default: { model: true, effort: true, agent: true, taskTime: false, tokens: false, dapiUsage: false },
14579
+ properties: {
14580
+ model: {
14581
+ type: "boolean",
14582
+ default: true,
14583
+ description: "Show model name in AI card status line footer."
14584
+ },
14585
+ effort: {
14586
+ type: "boolean",
14587
+ default: true,
14588
+ description: "Show thinking effort level in AI card status line footer."
14589
+ },
14590
+ agent: {
14591
+ type: "boolean",
14592
+ default: true,
14593
+ description: "Show agent display name in AI card status line footer."
14594
+ },
14595
+ taskTime: {
14596
+ type: "boolean",
14597
+ default: false,
14598
+ description: "Show task elapsed time in AI card status line footer."
14599
+ },
14600
+ tokens: {
14601
+ type: "boolean",
14602
+ default: false,
14603
+ description: "Show token usage summary (input/output/cache) in AI card status line footer."
14604
+ },
14605
+ dapiUsage: {
14606
+ type: "boolean",
14607
+ default: false,
14608
+ description: "Show DingTalk API call count in AI card status line footer."
14609
+ }
14610
+ }
14611
+ }
14612
+ }
14613
+ }
14614
+ }
14615
+ }
14616
+ },
14617
+ uiHints: {
14618
+ "": {
14619
+ label: "DingTalk",
14620
+ help: "DingTalk Stream-mode channel configuration including auth, delivery, targeting, and AI Card behavior."
14621
+ },
14622
+ name: {
14623
+ label: "Display Name",
14624
+ help: "Optional display name for the default DingTalk account shown in host configuration surfaces."
14625
+ },
14626
+ enabled: {
14627
+ label: "Enabled",
14628
+ help: "Turn the DingTalk channel on or off without deleting the saved configuration."
14629
+ },
14630
+ agentId: {
14631
+ label: "Legacy Agent ID",
14632
+ help: "Deprecated compatibility field from older setup flows. The current runtime ignores it, so leave it empty in new configs."
14633
+ },
14634
+ corpId: {
14635
+ label: "Legacy Corp ID",
14636
+ help: "Deprecated compatibility field from older enterprise app setup flows. Stream mode no longer needs or reads it."
14637
+ },
14638
+ clientId: {
14639
+ label: "DingTalk Client ID",
14640
+ help: "App Key used to authenticate DingTalk API requests."
14641
+ },
14642
+ clientSecret: {
14643
+ label: "DingTalk Client Secret",
14644
+ help: "App Secret used to obtain DingTalk access tokens. SecretInput references are resolved by configured OpenClaw secret providers.",
14645
+ sensitive: true
14646
+ },
14647
+ dmPolicy: {
14648
+ label: "Direct Message Policy",
14649
+ help: "Choose who can talk to the bot in direct messages: open, host pairing only, or explicit allowlist."
14650
+ },
14651
+ groupPolicy: {
14652
+ label: "Group Message Policy",
14653
+ help: "Choose whether group messages are open to all groups, restricted by allowlist, or completely disabled."
14654
+ },
14655
+ allowFrom: {
14656
+ label: "DM Allowlist",
14657
+ help: "User IDs allowed when direct message policy is set to allowlist."
14658
+ },
14659
+ groupAllowFrom: {
14660
+ label: "Group Sender Allowlist",
14661
+ help: "Sender IDs allowed when group policy is allowlist or when you want tighter group sender restrictions."
14662
+ },
14663
+ messageType: {
14664
+ label: "Reply Message Type",
14665
+ help: "Choose markdown for standard replies or card for AI Card delivery."
14666
+ },
14667
+ ackReaction: {
14668
+ label: "Ack Reaction",
14669
+ help: "Native acknowledgement reaction mode. Supports preset values or a custom string for backward compatibility."
14670
+ },
14671
+ displayNameResolution: {
14672
+ label: "Display Name Resolution",
14673
+ help: "Keep disabled unless you explicitly want learned display-name targeting for all callers."
14674
+ },
14675
+ contextVisibility: {
14676
+ label: "Context Visibility",
14677
+ help: "Host-level context filtering. allowlist_quote is the safest advanced mode when you only want explicit quote/reply context."
14678
+ },
14679
+ mediaUrlAllowlist: {
14680
+ label: "Media URL Allowlist",
14681
+ help: "Allow remote media downloads only from the specified hosts, IPs, or CIDR ranges."
14682
+ },
14683
+ journalTTLDays: {
14684
+ label: "Message Context TTL (days)",
14685
+ help: "Retention period for short-lived quoted message and media recovery context."
14686
+ },
14687
+ debug: {
14688
+ label: "Debug Logging",
14689
+ help: "Enable verbose DingTalk channel debug logs for diagnostics."
14690
+ },
14691
+ cardTemplateId: {
14692
+ label: "AI Card Template ID",
14693
+ help: "Deprecated and ignored. AI card replies now always use the built-in DingTalk template contract."
14694
+ },
14695
+ cardTemplateKey: {
14696
+ label: "AI Card Template Field Key",
14697
+ help: "Deprecated and ignored. The built-in AI card contract owns the streaming field mapping."
14698
+ },
14699
+ groups: {
14700
+ label: "Per-Group Overrides",
14701
+ help: "Configure conversationId-specific overrides, or use * as a wildcard fallback."
14702
+ },
14703
+ "groups.systemPrompt": {
14704
+ label: "Group System Prompt",
14705
+ help: "Extra system prompt appended only for the selected group."
14706
+ },
14707
+ "groups.requireMention": {
14708
+ label: "Require Mention",
14709
+ help: "Require an explicit @mention before the bot answers in that group."
14710
+ },
14711
+ "groups.groupAllowFrom": {
14712
+ label: "Per-Group Sender Allowlist",
14713
+ help: "Optional sender allowlist that overrides or tightens the channel-level group access rule."
14714
+ },
14715
+ maxConnectionAttempts: {
14716
+ label: "Max Connection Attempts",
14717
+ help: "How many connection attempts are allowed within a reconnect cycle before the cycle backs off or ends."
14718
+ },
14719
+ initialReconnectDelay: {
14720
+ label: "Initial Reconnect Delay",
14721
+ help: "Initial reconnect backoff delay in milliseconds."
14722
+ },
14723
+ maxReconnectDelay: {
14724
+ label: "Max Reconnect Delay",
14725
+ help: "Upper bound for reconnect backoff delay in milliseconds."
14726
+ },
14727
+ reconnectJitter: {
14728
+ label: "Reconnect Jitter",
14729
+ help: "Randomization factor added to reconnect backoff to reduce synchronized reconnect storms."
14730
+ },
14731
+ maxReconnectCycles: {
14732
+ label: "Max Reconnect Cycles",
14733
+ help: "Maximum reconnect cycles before the channel stops retrying and waits for a later restart."
14734
+ },
14735
+ reconnectDeadlineMs: {
14736
+ label: "Reconnect Cycle Deadline",
14737
+ help: "Time limit in milliseconds for one reconnect cycle before starting a fresh cycle."
14738
+ },
14739
+ useConnectionManager: {
14740
+ label: "Use Connection Manager",
14741
+ help: "Recommended default. Disable only when you intentionally rely on the DingTalk client keepAlive and autoReconnect behavior."
14742
+ },
14743
+ mediaMaxMb: {
14744
+ label: "Max Media Size (MB)",
14745
+ help: "Maximum inbound media size accepted by the plugin. Leave empty to use the runtime default."
14746
+ },
14747
+ keepAlive: {
14748
+ label: "Stream KeepAlive",
14749
+ help: "Optional explicit heartbeat toggle for the underlying Stream client."
14750
+ },
14751
+ bypassProxyForSend: {
14752
+ label: "Bypass Proxy For Send",
14753
+ help: "Skip global or system HTTP(S) proxy settings for DingTalk send, upload, and card APIs."
14754
+ },
14755
+ cardRealTimeStream: {
14756
+ label: "Legacy Real-Time Card Stream",
14757
+ help: "Deprecated compatibility flag. When true and cardStreamingMode is unset, runtime falls back to all-streaming. Do not use in new configs."
14758
+ },
14759
+ cardStreamingMode: {
14760
+ label: "Card Streaming Mode",
14761
+ help: "Choose off, answer, or all. answer streams only answer text, while all streams answer plus reasoning or thinking text."
14762
+ },
14763
+ cardStreamInterval: {
14764
+ label: "Card Stream Interval (ms)",
14765
+ help: "Throttle interval in milliseconds between AI card incremental updates."
14766
+ },
14767
+ aicardDegradeMs: {
14768
+ label: "AI Card Degrade Window (ms)",
14769
+ help: "Cooldown window after AI card trigger errors. Replies fall back to non-card delivery during this period."
14770
+ },
14771
+ learningEnabled: {
14772
+ label: "Learning Enabled",
14773
+ help: "Enable the local feedback-learning loop for notes, reflections, and learning commands."
14774
+ },
14775
+ learningAutoApply: {
14776
+ label: "Learning Auto Apply",
14777
+ help: "Automatically apply generated learning output into session notes or global rules when available."
14778
+ },
14779
+ learningNoteTtlMs: {
14780
+ label: "Learning Note TTL (ms)",
14781
+ help: "Retention period for temporary learning notes."
14782
+ },
14783
+ convertMarkdownTables: {
14784
+ label: "Convert Markdown Tables",
14785
+ help: "Flatten markdown tables into plain text before sending when you want more consistent DingTalk rendering."
14786
+ },
14787
+ cardAtSender: {
14788
+ label: "Card Mention Follow-Up",
14789
+ help: "Send a follow-up @mention text after card finalization in group chats. Any non-empty string becomes the message body."
14790
+ },
14791
+ cardStatusLine: {
14792
+ label: "Status Line",
14793
+ help: "Configure which information segments appear in the AI card footer status line."
14794
+ },
14795
+ "cardStatusLine.model": {
14796
+ label: "Model Name",
14797
+ help: "Show the LLM model name in the AI card footer status line."
14798
+ },
14799
+ "cardStatusLine.effort": {
14800
+ label: "Thinking Effort",
14801
+ help: "Show the thinking effort level (e.g. high, medium) in the AI card footer."
14802
+ },
14803
+ "cardStatusLine.agent": {
14804
+ label: "Agent Name",
14805
+ help: "Show the agent display name in the AI card footer."
14806
+ },
14807
+ "cardStatusLine.taskTime": {
14808
+ label: "Task Time",
14809
+ help: "Show elapsed task time in the AI card footer."
14810
+ },
14811
+ "cardStatusLine.tokens": {
14812
+ label: "Token Usage",
14813
+ help: "Show a compact token usage summary (input/output/cache) in the AI card footer."
14814
+ },
14815
+ "cardStatusLine.dapiUsage": {
14816
+ label: "API Calls",
14817
+ help: "Show the DingTalk API call count in the AI card footer."
14818
+ },
14819
+ showThinkingStream: {
14820
+ label: "Legacy Show Thinking Stream",
14821
+ help: "Deprecated compatibility field from older card streaming behavior. The current runtime ignores it."
14822
+ },
14823
+ asyncMode: {
14824
+ label: "Legacy Async Mode",
14825
+ help: "Deprecated compatibility field from older host or runtime integrations. The current runtime ignores it."
14826
+ },
14827
+ accounts: {
14828
+ label: "Named Accounts",
14829
+ help: "Create named DingTalk accounts when one plugin instance must serve multiple apps or tenants."
14830
+ },
14831
+ "proactivePermissionHint.enabled": {
14832
+ label: "Proactive Permission Hint Enabled",
14833
+ help: "Show a reminder when DingTalk proactive-send permission has not been granted."
14834
+ },
14835
+ "proactivePermissionHint.cooldownHours": {
14836
+ label: "Proactive Hint Cooldown (hours)",
14837
+ help: "Minimum cooldown before the same proactive permission hint can be shown again."
14838
+ }
14839
+ }
14840
+ }
14841
+ }
13953
14842
  };
13954
- var DingTalkAccountConfigSchema = z2.object(DingTalkAccountConfigShape);
13955
- var DingTalkConfigSchema = DingTalkAccountConfigSchema.extend({
13956
- /** Multi-account configuration */
13957
- accounts: z2.record(z2.string(), DingTalkAccountConfigSchema.optional()).optional()
13958
- });
13959
14843
 
13960
14844
  // src/gateway/channel-gateway.ts
13961
14845
  import { DWClient, TOPIC_CARD, TOPIC_ROBOT } from "dingtalk-stream";
14846
+ import { listAgentIds } from "openclaw/plugin-sdk/agent-runtime";
13962
14847
 
13963
14848
  // src/card/card-stop-handler.ts
13964
14849
  async function stopCardRun(params) {
@@ -14929,11 +15814,69 @@ function createDingTalkGateway() {
14929
15814
  debug: config.debug,
14930
15815
  baseLog: ctx.log
14931
15816
  });
15817
+ try {
15818
+ const knownGroupIds = /* @__PURE__ */ new Set();
15819
+ for (const group of listKnownGroupTargets({
15820
+ storePath: accountStorePath,
15821
+ accountId: account.accountId
15822
+ })) {
15823
+ registerPeerId(group.conversationId);
15824
+ knownGroupIds.add(group.conversationId.toLowerCase());
15825
+ }
15826
+ const runtime = getDingTalkRuntime();
15827
+ for (const agentId of listAgentIds(cfg)) {
15828
+ const sessionStorePath = runtime.channel.session.resolveStorePath(cfg.session?.store, {
15829
+ agentId
15830
+ });
15831
+ for (const { entry } of runtime.agent.session.listSessionEntries({
15832
+ agentId,
15833
+ storePath: sessionStorePath,
15834
+ readConsistency: "latest"
15835
+ })) {
15836
+ const isDingTalkSession = [
15837
+ entry.lastChannel,
15838
+ entry.channel,
15839
+ entry.origin?.provider,
15840
+ entry.origin?.surface
15841
+ ].some((value) => value === "dingtalk");
15842
+ const sessionAccountId = entry.lastAccountId || entry.origin?.accountId;
15843
+ if (!isDingTalkSession || sessionAccountId !== account.accountId) {
15844
+ continue;
15845
+ }
15846
+ for (const peerId of [entry.lastTo, entry.origin?.from, entry.origin?.to]) {
15847
+ if (typeof peerId !== "string" || !peerId.startsWith("cid")) {
15848
+ continue;
15849
+ }
15850
+ registerPeerId(peerId);
15851
+ const normalizedPeerId = peerId.toLowerCase();
15852
+ if (!knownGroupIds.has(normalizedPeerId)) {
15853
+ upsertObservedGroupTarget({
15854
+ storePath: accountStorePath,
15855
+ accountId: account.accountId,
15856
+ conversationId: peerId,
15857
+ seenAt: entry.updatedAt
15858
+ });
15859
+ knownGroupIds.add(normalizedPeerId);
15860
+ }
15861
+ }
15862
+ }
15863
+ }
15864
+ for (const user of listKnownUserTargets({
15865
+ storePath: accountStorePath,
15866
+ accountId: account.accountId
15867
+ })) {
15868
+ registerPeerId(user.canonicalUserId);
15869
+ registerPeerId(user.staffId || "");
15870
+ registerPeerId(user.senderId);
15871
+ }
15872
+ } catch (err) {
15873
+ pluginLog?.warn?.(
15874
+ `[${account.accountId}] Failed to restore peer IDs: ${err instanceof Error ? err.message : String(err)}`
15875
+ );
15876
+ }
14932
15877
  const runtimeConfig = await resolveRuntimeConfig(config, pluginLog);
14933
15878
  setCurrentLogger(pluginLog, account.accountId);
14934
15879
  pluginLog?.info?.(`[${account.accountId}] Initializing DingTalk Stream client...`);
14935
- preloadPeerIdsFromSessions();
14936
- pluginLog?.debug?.(`[${account.accountId}] Peer ID registry preloaded from sessions`);
14937
15880
  cleanupOrphanedTempFiles(pluginLog);
14938
15881
  try {
14939
15882
  const recovered = await recoverPendingCardsForAccount(
@@ -16013,10 +16956,9 @@ function describeDingTalkMessageTool(cfg) {
16013
16956
  if (!configured && !(config.accounts && Object.keys(config.accounts).length > 0)) {
16014
16957
  return { actions: [], capabilities: [], schema: null };
16015
16958
  }
16016
- const hasCardMode = config.messageType === "card" || config.accounts && Object.values(config.accounts).some((account) => account?.messageType === "card");
16017
16959
  return {
16018
16960
  actions: ["send"],
16019
- capabilities: hasCardMode ? ["cards"] : [],
16961
+ capabilities: [],
16020
16962
  schema: null
16021
16963
  };
16022
16964
  }
@@ -16485,7 +17427,7 @@ var dingtalkPlugin = {
16485
17427
  blurb: "\u9489\u9489\u4F01\u4E1A\u5185\u90E8\u673A\u5668\u4EBA\uFF0C\u4F7F\u7528 Stream \u6A21\u5F0F\uFF0C\u65E0\u9700\u516C\u7F51 IP\u3002",
16486
17428
  aliases: ["dd", "ding"]
16487
17429
  },
16488
- configSchema: buildChannelConfigSchema(DingTalkConfigSchema),
17430
+ configSchema: buildJsonChannelConfigSchema(openclaw_plugin_default.channelConfigs.dingtalk.schema),
16489
17431
  setup: dingtalkSetupAdapter,
16490
17432
  setupWizard: dingtalkSetupWizard,
16491
17433
  capabilities: {
@@ -16936,7 +17878,7 @@ var dingtalkEntry = defineChannelPluginEntry({
16936
17878
  );
16937
17879
  }
16938
17880
  });
16939
- var index_default = {
17881
+ var pluginDefinition = {
16940
17882
  ...dingtalkEntry,
16941
17883
  register(api) {
16942
17884
  const registrationMode = api.registrationMode;
@@ -16950,9 +17892,9 @@ var index_default = {
16950
17892
  dingtalkEntry.register(api);
16951
17893
  }
16952
17894
  };
17895
+ var index_default = pluginDefinition;
16953
17896
  export {
16954
17897
  index_default as default,
16955
17898
  dingtalkPlugin,
16956
17899
  setDingTalkRuntime
16957
17900
  };
16958
- //# sourceMappingURL=index.js.map