@soimy/dingtalk 3.6.9 → 3.6.10

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 (178) hide show
  1. package/dist/index.d.ts +3 -12
  2. package/dist/index.js +1030 -318
  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 +0 -1
  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/quoted-context.d.ts +0 -1
  54. package/dist/src/messaging/quoted-file-service.d.ts +0 -1
  55. package/dist/src/messaging/quoted-ref.d.ts +0 -1
  56. package/dist/src/onboarding.d.ts +0 -1
  57. package/dist/src/path-utils.d.ts +0 -1
  58. package/dist/src/peer-id-registry.d.ts +0 -12
  59. package/dist/src/persistence-store.d.ts +0 -1
  60. package/dist/src/platform/channel-status.d.ts +0 -1
  61. package/dist/src/plugin-sdk-channel-actions-augment.d.ts +0 -1
  62. package/dist/src/proactive-risk-registry.d.ts +0 -1
  63. package/dist/src/reply-strategy-card.d.ts +0 -1
  64. package/dist/src/reply-strategy-markdown.d.ts +0 -1
  65. package/dist/src/reply-strategy-types.d.ts +0 -1
  66. package/dist/src/reply-strategy-with-reaction.d.ts +0 -1
  67. package/dist/src/reply-strategy.d.ts +0 -1
  68. package/dist/src/run-usage-store.d.ts +0 -1
  69. package/dist/src/runtime.d.ts +0 -1
  70. package/dist/src/secret-input.d.ts +3 -3
  71. package/dist/src/send-service.d.ts +0 -1
  72. package/dist/src/session-command-service.d.ts +0 -1
  73. package/dist/src/session-lock.d.ts +0 -1
  74. package/dist/src/session-peer-store.d.ts +0 -1
  75. package/dist/src/session-routing.d.ts +0 -1
  76. package/dist/src/session-state.d.ts +0 -1
  77. package/dist/src/signature.d.ts +0 -1
  78. package/dist/src/targeting/agent-name-matcher.d.ts +0 -1
  79. package/dist/src/targeting/agent-routing.d.ts +0 -1
  80. package/dist/src/targeting/group-members-store.d.ts +0 -1
  81. package/dist/src/targeting/target-directory-adapter.d.ts +0 -1
  82. package/dist/src/targeting/target-directory-store.d.ts +0 -1
  83. package/dist/src/targeting/target-input.d.ts +0 -1
  84. package/dist/src/types.d.ts +0 -1
  85. package/dist/src/utils.d.ts +0 -1
  86. package/index.ts +8 -2
  87. package/openclaw.plugin.json +4 -3
  88. package/package.json +10 -8
  89. package/src/channel.ts +4 -3
  90. package/src/gateway/channel-gateway.ts +67 -4
  91. package/src/messaging/channel-actions.ts +1 -4
  92. package/src/peer-id-registry.ts +0 -100
  93. package/src/secret-input.ts +33 -33
  94. package/dist/index.d.ts.map +0 -1
  95. package/dist/index.js.map +0 -7
  96. package/dist/src/access-control.d.ts.map +0 -1
  97. package/dist/src/ack-reaction/dynamic-ack-reaction-controller.d.ts.map +0 -1
  98. package/dist/src/ack-reaction/dynamic-ack-reaction-events.d.ts.map +0 -1
  99. package/dist/src/ack-reaction/dynamic-ack-reaction-progress.d.ts.map +0 -1
  100. package/dist/src/ack-reaction-classifier.d.ts.map +0 -1
  101. package/dist/src/ack-reaction-service.d.ts.map +0 -1
  102. package/dist/src/auth.d.ts.map +0 -1
  103. package/dist/src/card/ask-user-question-context.d.ts.map +0 -1
  104. package/dist/src/card/ask-user-question-store.d.ts.map +0 -1
  105. package/dist/src/card/ask-user-question.d.ts.map +0 -1
  106. package/dist/src/card/card-action-handler.d.ts.map +0 -1
  107. package/dist/src/card/card-markdown-image-reroute.d.ts.map +0 -1
  108. package/dist/src/card/card-run-registry.d.ts.map +0 -1
  109. package/dist/src/card/card-stop-handler.d.ts.map +0 -1
  110. package/dist/src/card/card-streaming-mode.d.ts.map +0 -1
  111. package/dist/src/card/card-template.d.ts.map +0 -1
  112. package/dist/src/card/reasoning-answer-split.d.ts.map +0 -1
  113. package/dist/src/card/reasoning-block-assembler.d.ts.map +0 -1
  114. package/dist/src/card/statusline-renderer.d.ts.map +0 -1
  115. package/dist/src/card/task-model-metadata.d.ts.map +0 -1
  116. package/dist/src/card-callback-service.d.ts.map +0 -1
  117. package/dist/src/card-draft-controller.d.ts.map +0 -1
  118. package/dist/src/card-service.d.ts.map +0 -1
  119. package/dist/src/channel.d.ts.map +0 -1
  120. package/dist/src/command/card-stop-command.d.ts.map +0 -1
  121. package/dist/src/command/inbound-command-dispatch-service.d.ts.map +0 -1
  122. package/dist/src/config-schema.d.ts.map +0 -1
  123. package/dist/src/config.d.ts.map +0 -1
  124. package/dist/src/connection-manager.d.ts.map +0 -1
  125. package/dist/src/dedup.d.ts.map +0 -1
  126. package/dist/src/device-registration.d.ts.map +0 -1
  127. package/dist/src/docs-service.d.ts.map +0 -1
  128. package/dist/src/draft-stream-loop.d.ts.map +0 -1
  129. package/dist/src/feedback-learning-service.d.ts.map +0 -1
  130. package/dist/src/feedback-learning-store.d.ts.map +0 -1
  131. package/dist/src/gateway/channel-gateway.d.ts.map +0 -1
  132. package/dist/src/gateway/inbound-session-queue-dispatcher.d.ts.map +0 -1
  133. package/dist/src/gateway/inbound-session-queue.d.ts.map +0 -1
  134. package/dist/src/gateway/reply-session-conflict.d.ts.map +0 -1
  135. package/dist/src/http-client.d.ts.map +0 -1
  136. package/dist/src/inbound-handler.d.ts.map +0 -1
  137. package/dist/src/learning-command-service.d.ts.map +0 -1
  138. package/dist/src/logger-context.d.ts.map +0 -1
  139. package/dist/src/media-utils.d.ts.map +0 -1
  140. package/dist/src/message-context-store.d.ts.map +0 -1
  141. package/dist/src/message-utils.d.ts.map +0 -1
  142. package/dist/src/messaging/attachment-text-extractor.d.ts.map +0 -1
  143. package/dist/src/messaging/btw-deliver.d.ts.map +0 -1
  144. package/dist/src/messaging/channel-actions.d.ts.map +0 -1
  145. package/dist/src/messaging/channel-outbound.d.ts.map +0 -1
  146. package/dist/src/messaging/quoted-context.d.ts.map +0 -1
  147. package/dist/src/messaging/quoted-file-service.d.ts.map +0 -1
  148. package/dist/src/messaging/quoted-ref.d.ts.map +0 -1
  149. package/dist/src/onboarding.d.ts.map +0 -1
  150. package/dist/src/path-utils.d.ts.map +0 -1
  151. package/dist/src/peer-id-registry.d.ts.map +0 -1
  152. package/dist/src/persistence-store.d.ts.map +0 -1
  153. package/dist/src/platform/channel-status.d.ts.map +0 -1
  154. package/dist/src/plugin-sdk-channel-actions-augment.d.ts.map +0 -1
  155. package/dist/src/proactive-risk-registry.d.ts.map +0 -1
  156. package/dist/src/reply-strategy-card.d.ts.map +0 -1
  157. package/dist/src/reply-strategy-markdown.d.ts.map +0 -1
  158. package/dist/src/reply-strategy-types.d.ts.map +0 -1
  159. package/dist/src/reply-strategy-with-reaction.d.ts.map +0 -1
  160. package/dist/src/reply-strategy.d.ts.map +0 -1
  161. package/dist/src/run-usage-store.d.ts.map +0 -1
  162. package/dist/src/runtime.d.ts.map +0 -1
  163. package/dist/src/secret-input.d.ts.map +0 -1
  164. package/dist/src/send-service.d.ts.map +0 -1
  165. package/dist/src/session-command-service.d.ts.map +0 -1
  166. package/dist/src/session-lock.d.ts.map +0 -1
  167. package/dist/src/session-peer-store.d.ts.map +0 -1
  168. package/dist/src/session-routing.d.ts.map +0 -1
  169. package/dist/src/session-state.d.ts.map +0 -1
  170. package/dist/src/signature.d.ts.map +0 -1
  171. package/dist/src/targeting/agent-name-matcher.d.ts.map +0 -1
  172. package/dist/src/targeting/agent-routing.d.ts.map +0 -1
  173. package/dist/src/targeting/group-members-store.d.ts.map +0 -1
  174. package/dist/src/targeting/target-directory-adapter.d.ts.map +0 -1
  175. package/dist/src/targeting/target-directory-store.d.ts.map +0 -1
  176. package/dist/src/targeting/target-input.d.ts.map +0 -1
  177. package/dist/src/types.d.ts.map +0 -1
  178. 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 = {};
89
+ }
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 };
134
101
  }
135
- const failure = buildSecretInputFailure(value, "environment variable is not set or is empty");
136
- log?.warn?.("[DingTalk][SecretInput] Failed to resolve env secret", {
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) {
@@ -1679,110 +1683,20 @@ var DINGTALK_ASK_USER_CARD_TEMPLATE = Object.freeze({
1679
1683
  templateId: BUILTIN_DINGTALK_ASK_USER_CARD_TEMPLATE_ID
1680
1684
  });
1681
1685
 
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
1686
  // src/peer-id-registry.ts
1711
1687
  var peerIdMap = /* @__PURE__ */ new Map();
1712
- var preloaded = false;
1713
1688
  function registerPeerId(originalId) {
1714
1689
  if (!originalId) {
1715
1690
  return;
1716
1691
  }
1717
1692
  peerIdMap.set(originalId.toLowerCase(), originalId);
1718
1693
  }
1719
- function maybeRegisterDingTalkGroupPeerId(value) {
1720
- if (typeof value === "string" && value.startsWith("cid")) {
1721
- registerPeerId(value);
1722
- }
1723
- }
1724
1694
  function resolveOriginalPeerId(id) {
1725
1695
  if (!id) {
1726
1696
  return id;
1727
1697
  }
1728
- if (!preloaded) {
1729
- preloadPeerIdsFromSessions();
1730
- }
1731
1698
  return peerIdMap.get(id.toLowerCase()) || id;
1732
1699
  }
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
1700
 
1787
1701
  // src/persistence-store.ts
1788
1702
  import * as fs2 from "node:fs";
@@ -3639,18 +3553,6 @@ function resolveConfiguredTaskModelMetadata(params) {
3639
3553
 
3640
3554
  // src/command/card-stop-command.ts
3641
3555
  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
3556
  async function dispatchDingTalkCardStopCommand(params) {
3655
3557
  const rt = getDingTalkRuntime();
3656
3558
  const { sessionKey: commandSessionKey, commandTargetSessionKey } = resolveNativeCommandSessionTargets({
@@ -5161,9 +5063,32 @@ function pickQueueBusyAckPhrase(seed) {
5161
5063
  // src/send-service.ts
5162
5064
  import * as path6 from "node:path";
5163
5065
 
5066
+ // src/logger-context.ts
5067
+ var currentLogger;
5068
+ var loggerByAccountId = /* @__PURE__ */ new Map();
5069
+ function setCurrentLogger(log, accountId) {
5070
+ currentLogger = log;
5071
+ const normalizedAccountId = typeof accountId === "string" ? accountId.trim() : "";
5072
+ if (!normalizedAccountId) {
5073
+ return;
5074
+ }
5075
+ if (log) {
5076
+ loggerByAccountId.set(normalizedAccountId, log);
5077
+ return;
5078
+ }
5079
+ loggerByAccountId.delete(normalizedAccountId);
5080
+ }
5081
+ function getLogger(accountId) {
5082
+ const normalizedAccountId = typeof accountId === "string" ? accountId.trim() : "";
5083
+ if (normalizedAccountId) {
5084
+ return loggerByAccountId.get(normalizedAccountId);
5085
+ }
5086
+ return currentLogger;
5087
+ }
5088
+
5164
5089
  // src/media-utils.ts
5165
5090
  import { randomUUID as randomUUID3 } from "node:crypto";
5166
- import * as os4 from "node:os";
5091
+ import * as os3 from "node:os";
5167
5092
  import * as path5 from "node:path";
5168
5093
  import { promises as fsPromises } from "node:fs";
5169
5094
  import { lookup as dnsLookup } from "node:dns/promises";
@@ -5319,7 +5244,7 @@ async function prepareVoiceUploadPath(mediaPath, log) {
5319
5244
  if (DINGTALK_VOICE_UPLOAD_EXTENSIONS.has(ext)) {
5320
5245
  return { path: mediaPath };
5321
5246
  }
5322
- const outputPath = path5.join(os4.tmpdir(), `dingtalk_voice_${randomUUID3()}.ogg`);
5247
+ const outputPath = path5.join(os3.tmpdir(), `dingtalk_voice_${randomUUID3()}.ogg`);
5323
5248
  await runFfmpeg([
5324
5249
  "-y",
5325
5250
  "-i",
@@ -5684,7 +5609,7 @@ async function prepareMediaInput(input, log, mediaUrlAllowlist) {
5684
5609
  const contentType = typeof response.headers?.["content-type"] === "string" ? response.headers["content-type"] : void 0;
5685
5610
  const urlPath = currentUrl.pathname;
5686
5611
  const ext = path5.extname(urlPath) || detectExtensionFromContentType(contentType) || ".bin";
5687
- const tempPath = path5.join(os4.tmpdir(), `dingtalk_${randomUUID3()}${ext}`);
5612
+ const tempPath = path5.join(os3.tmpdir(), `dingtalk_${randomUUID3()}${ext}`);
5688
5613
  const buffer = Buffer.isBuffer(response.data) ? response.data : Buffer.from(response.data);
5689
5614
  await fsPromises.writeFile(tempPath, buffer);
5690
5615
  log?.debug?.(`[DingTalk] Downloaded remote media to temp file: ${tempPath}`);
@@ -13826,139 +13751,869 @@ function registerDingTalkAskUserQuestionTool(api) {
13826
13751
  }
13827
13752
 
13828
13753
  // src/channel.ts
13829
- import { buildChannelConfigSchema } from "openclaw/plugin-sdk/core";
13754
+ import { buildJsonChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
13830
13755
 
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 })
13756
+ // openclaw.plugin.json
13757
+ var openclaw_plugin_default = {
13758
+ id: "dingtalk",
13759
+ name: "DingTalk",
13760
+ channels: ["dingtalk"],
13761
+ contracts: {
13762
+ tools: ["dingtalk_ask_user_question"]
13763
+ },
13764
+ configSchema: {
13765
+ type: "object",
13766
+ additionalProperties: true,
13767
+ properties: {}
13768
+ },
13769
+ channelConfigs: {
13770
+ dingtalk: {
13771
+ label: "DingTalk",
13772
+ description: "\u9489\u9489\u4F01\u4E1A\u5185\u90E8\u673A\u5668\u4EBA\uFF0C\u4F7F\u7528 Stream \u6A21\u5F0F\uFF0C\u65E0\u9700\u516C\u7F51 IP\u3002",
13773
+ schema: {
13774
+ type: "object",
13775
+ additionalProperties: false,
13776
+ properties: {
13777
+ name: {
13778
+ type: "string",
13779
+ description: "Optional display name shown for this DingTalk channel or default account."
13780
+ },
13781
+ enabled: {
13782
+ type: "boolean",
13783
+ default: true,
13784
+ description: "Enable or disable this DingTalk channel configuration without deleting saved credentials."
13785
+ },
13786
+ agentId: {
13787
+ type: "integer",
13788
+ description: "Legacy compatibility field from older setup flows. The current Stream-mode runtime no longer reads this value, so new configs should omit it."
13789
+ },
13790
+ corpId: {
13791
+ type: "string",
13792
+ description: "Legacy compatibility field from older enterprise app setup flows. The current Stream-mode runtime no longer needs or reads this value."
13793
+ },
13794
+ clientId: {
13795
+ type: "string",
13796
+ description: "DingTalk App Key (Client ID) used to authenticate API and Stream connections."
13797
+ },
13798
+ clientSecret: {
13799
+ anyOf: [
13800
+ { type: "string" },
13801
+ {
13802
+ type: "object",
13803
+ additionalProperties: false,
13804
+ required: ["source", "provider", "id"],
13805
+ properties: {
13806
+ source: { type: "string", enum: ["env", "file"] },
13807
+ provider: {
13808
+ type: "string",
13809
+ minLength: 1,
13810
+ maxLength: 1024,
13811
+ pattern: "^[^:>]+$"
13812
+ },
13813
+ id: {
13814
+ type: "string",
13815
+ minLength: 1,
13816
+ maxLength: 1024,
13817
+ pattern: "^[^>]+$"
13818
+ }
13819
+ }
13820
+ }
13821
+ ],
13822
+ description: "DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. Supports env/file SecretInput references resolved by configured OpenClaw secret providers."
13823
+ },
13824
+ dmPolicy: {
13825
+ type: "string",
13826
+ enum: ["open", "pairing", "allowlist"],
13827
+ default: "open",
13828
+ description: "Direct-message access policy. open allows any DM, pairing requires host pairing, and allowlist only permits IDs listed in allowFrom."
13829
+ },
13830
+ groupPolicy: {
13831
+ type: "string",
13832
+ enum: ["open", "allowlist", "disabled"],
13833
+ default: "open",
13834
+ description: "Group-message access policy. open allows all groups, allowlist restricts by groupAllowFrom, and disabled blocks group handling."
13835
+ },
13836
+ allowFrom: {
13837
+ type: "array",
13838
+ items: { type: "string" },
13839
+ description: "User IDs allowed when dmPolicy is allowlist. Accepts DingTalk IDs or host-normalized IDs."
13840
+ },
13841
+ groupAllowFrom: {
13842
+ type: "array",
13843
+ items: { type: "string" },
13844
+ description: "Sender IDs allowed when groupPolicy is allowlist. Leave empty unless you want per-sender restrictions in groups."
13845
+ },
13846
+ displayNameResolution: {
13847
+ type: "string",
13848
+ enum: ["disabled", "all"],
13849
+ default: "disabled",
13850
+ 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."
13851
+ },
13852
+ contextVisibility: {
13853
+ type: "string",
13854
+ enum: ["all", "allowlist", "allowlist_quote"],
13855
+ 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."
13856
+ },
13857
+ mediaUrlAllowlist: {
13858
+ type: "array",
13859
+ items: { type: "string" },
13860
+ description: "Allowed download hosts, IPs, or CIDRs for remote media fetches. Use this for internal file servers or custom CDN domains."
13861
+ },
13862
+ ackReaction: {
13863
+ anyOf: [
13864
+ { type: "string", const: "" },
13865
+ { type: "string", enum: ["off", "emoji", "kaomoji"] },
13866
+ { type: "string", minLength: 1 }
13867
+ ],
13868
+ description: "Acknowledgement reaction mode. Supports off, emoji, kaomoji, or a custom compatibility string."
13869
+ },
13870
+ journalTTLDays: {
13871
+ type: "integer",
13872
+ minimum: 1,
13873
+ default: 7,
13874
+ description: "Retention window in days for short-lived message context used by quoting, media recovery, and related reply context features."
13875
+ },
13876
+ debug: {
13877
+ type: "boolean",
13878
+ default: false,
13879
+ description: "Enable verbose DingTalk channel debug logging."
13880
+ },
13881
+ messageType: {
13882
+ type: "string",
13883
+ enum: ["markdown", "card"],
13884
+ default: "markdown",
13885
+ description: "Default reply delivery mode. markdown uses standard messages; card uses DingTalk AI cards when available."
13886
+ },
13887
+ cardTemplateId: {
13888
+ type: "string",
13889
+ description: "Deprecated and ignored. AI card replies now always use the built-in DingTalk template contract; keep only for compatibility with old configs."
13890
+ },
13891
+ cardTemplateKey: {
13892
+ type: "string",
13893
+ default: "content",
13894
+ description: "Deprecated and ignored. The built-in AI card contract owns the streaming field mapping; keep only for compatibility with old configs."
13895
+ },
13896
+ groups: {
13897
+ type: "object",
13898
+ description: 'Per-group overrides keyed by conversationId. Supports "*" as a wildcard fallback.',
13899
+ additionalProperties: {
13900
+ type: "object",
13901
+ additionalProperties: false,
13902
+ properties: {
13903
+ systemPrompt: {
13904
+ type: "string",
13905
+ description: "Additional system prompt appended for this group."
13906
+ },
13907
+ requireMention: {
13908
+ type: "boolean",
13909
+ description: "Require an explicit @mention before the bot answers in this group."
13910
+ },
13911
+ groupAllowFrom: {
13912
+ type: "array",
13913
+ items: { type: "string" },
13914
+ description: "Per-group sender allowlist used when you want tighter access control than the channel default."
13915
+ }
13916
+ }
13917
+ }
13918
+ },
13919
+ maxConnectionAttempts: {
13920
+ type: "integer",
13921
+ minimum: 1,
13922
+ default: 10,
13923
+ description: "Maximum connection attempts in a single reconnect cycle before backing off or giving up."
13924
+ },
13925
+ initialReconnectDelay: {
13926
+ type: "integer",
13927
+ minimum: 100,
13928
+ default: 1e3,
13929
+ description: "Initial reconnect backoff delay in milliseconds."
13930
+ },
13931
+ maxReconnectDelay: {
13932
+ type: "integer",
13933
+ minimum: 1e3,
13934
+ default: 6e4,
13935
+ description: "Upper bound for reconnect backoff delay in milliseconds."
13936
+ },
13937
+ reconnectJitter: {
13938
+ type: "number",
13939
+ minimum: 0,
13940
+ maximum: 1,
13941
+ default: 0.3,
13942
+ description: "Randomization factor added to reconnect backoff to avoid synchronized reconnect storms."
13943
+ },
13944
+ maxReconnectCycles: {
13945
+ type: "integer",
13946
+ minimum: 1,
13947
+ default: 10,
13948
+ description: "Maximum reconnect cycles before the channel stops retrying and waits for the next lifecycle restart."
13949
+ },
13950
+ reconnectDeadlineMs: {
13951
+ type: "integer",
13952
+ minimum: 5e3,
13953
+ default: 5e4,
13954
+ description: "Time limit in milliseconds for one reconnect cycle before starting a fresh cycle."
13955
+ },
13956
+ useConnectionManager: {
13957
+ type: "boolean",
13958
+ default: true,
13959
+ description: "Enable the plugin connection manager. Disable only when you intentionally rely on the DingTalk client keepAlive and autoReconnect behavior."
13960
+ },
13961
+ mediaMaxMb: {
13962
+ type: "integer",
13963
+ minimum: 1,
13964
+ description: "Maximum inbound media size in MB accepted by the plugin. When omitted, the runtime default is used."
13965
+ },
13966
+ keepAlive: {
13967
+ type: "boolean",
13968
+ description: "Enable the underlying Stream client heartbeat. When omitted, runtime derives a default from useConnectionManager."
13969
+ },
13970
+ bypassProxyForSend: {
13971
+ type: "boolean",
13972
+ default: false,
13973
+ description: "Bypass global or system HTTP(S) proxy settings for DingTalk send, upload, and card APIs."
13974
+ },
13975
+ proactivePermissionHint: {
13976
+ type: "object",
13977
+ description: "Controls the proactive-send permission reminder shown when a conversation has not granted send rights yet.",
13978
+ additionalProperties: false,
13979
+ properties: {
13980
+ enabled: {
13981
+ type: "boolean",
13982
+ default: true,
13983
+ description: "Show the proactive-send permission hint when the runtime detects missing DingTalk proactive permission."
13984
+ },
13985
+ cooldownHours: {
13986
+ type: "integer",
13987
+ minimum: 1,
13988
+ maximum: 720,
13989
+ default: 24,
13990
+ description: "Minimum cooldown in hours before the same proactive permission hint can be shown again."
13991
+ }
13992
+ }
13993
+ },
13994
+ cardRealTimeStream: {
13995
+ type: "boolean",
13996
+ default: false,
13997
+ description: 'Deprecated compatibility flag. If true and cardStreamingMode is unset, runtime falls back to cardStreamingMode="all". Do not use in new configs.'
13998
+ },
13999
+ cardStreamingMode: {
14000
+ type: "string",
14001
+ enum: ["off", "answer", "all"],
14002
+ description: "AI card incremental streaming mode. off disables streaming, answer streams answer text only, and all streams answer plus reasoning or thinking text."
14003
+ },
14004
+ cardStreamInterval: {
14005
+ type: "integer",
14006
+ minimum: 200,
14007
+ default: 1e3,
14008
+ description: "Throttle interval in milliseconds between AI card streaming updates."
14009
+ },
14010
+ aicardDegradeMs: {
14011
+ type: "integer",
14012
+ minimum: 6e4,
14013
+ default: 18e5,
14014
+ description: "Cooldown window in milliseconds after AI card trigger errors. During this window replies fall back to non-card delivery."
14015
+ },
14016
+ learningEnabled: {
14017
+ type: "boolean",
14018
+ description: "Enable the local feedback-learning loop for notes, reflections, and command-assisted learning."
14019
+ },
14020
+ learningAutoApply: {
14021
+ type: "boolean",
14022
+ description: "Automatically apply generated learning output into session notes or global rules when available."
14023
+ },
14024
+ learningNoteTtlMs: {
14025
+ type: "integer",
14026
+ minimum: 6e4,
14027
+ description: "Retention window in milliseconds for temporary learning notes."
14028
+ },
14029
+ convertMarkdownTables: {
14030
+ type: "boolean",
14031
+ default: true,
14032
+ description: "Convert markdown tables to plain text before sending when you want more consistent DingTalk rendering."
14033
+ },
14034
+ cardAtSender: {
14035
+ type: "string",
14036
+ description: "Send a follow-up @mention text after card finalization in group chats. Any non-empty string becomes the message body."
14037
+ },
14038
+ cardStatusLine: {
14039
+ type: "object",
14040
+ description: "Status line visibility toggles for the AI card footer.",
14041
+ additionalProperties: false,
14042
+ default: { model: true, effort: true, agent: true, taskTime: false, tokens: false, dapiUsage: false },
14043
+ properties: {
14044
+ model: {
14045
+ type: "boolean",
14046
+ default: true,
14047
+ description: "Show model name in AI card status line footer."
14048
+ },
14049
+ effort: {
14050
+ type: "boolean",
14051
+ default: true,
14052
+ description: "Show thinking effort level in AI card status line footer."
14053
+ },
14054
+ agent: {
14055
+ type: "boolean",
14056
+ default: true,
14057
+ description: "Show agent display name in AI card status line footer."
14058
+ },
14059
+ taskTime: {
14060
+ type: "boolean",
14061
+ default: false,
14062
+ description: "Show task elapsed time in AI card status line footer."
14063
+ },
14064
+ tokens: {
14065
+ type: "boolean",
14066
+ default: false,
14067
+ description: "Show token usage summary (input/output/cache) in AI card status line footer."
14068
+ },
14069
+ dapiUsage: {
14070
+ type: "boolean",
14071
+ default: false,
14072
+ description: "Show DingTalk API call count in AI card status line footer."
14073
+ }
14074
+ }
14075
+ },
14076
+ showThinkingStream: {
14077
+ type: "boolean",
14078
+ description: "Legacy compatibility field from older card streaming behavior. It is not used by the current runtime and new configs should omit it."
14079
+ },
14080
+ asyncMode: {
14081
+ type: "boolean",
14082
+ description: "Legacy compatibility field from older host or runtime integrations. It is not used by the current runtime and new configs should omit it."
14083
+ },
14084
+ accounts: {
14085
+ type: "object",
14086
+ description: "Named multi-account DingTalk configurations. Each account can override most top-level settings and inherits unspecified values from the top level.",
14087
+ additionalProperties: {
14088
+ type: "object",
14089
+ additionalProperties: false,
14090
+ properties: {
14091
+ name: {
14092
+ type: "string",
14093
+ description: "Optional display name shown for this named DingTalk account."
14094
+ },
14095
+ enabled: {
14096
+ type: "boolean",
14097
+ default: true,
14098
+ description: "Enable or disable this DingTalk account without deleting saved credentials."
14099
+ },
14100
+ clientId: {
14101
+ type: "string",
14102
+ description: "DingTalk App Key (Client ID) used to authenticate API and Stream connections."
14103
+ },
14104
+ clientSecret: {
14105
+ anyOf: [
14106
+ { type: "string" },
14107
+ {
14108
+ type: "object",
14109
+ additionalProperties: false,
14110
+ required: ["source", "provider", "id"],
14111
+ properties: {
14112
+ source: { type: "string", enum: ["env", "file"] },
14113
+ provider: {
14114
+ type: "string",
14115
+ minLength: 1,
14116
+ maxLength: 1024,
14117
+ pattern: "^[^:>]+$"
14118
+ },
14119
+ id: {
14120
+ type: "string",
14121
+ minLength: 1,
14122
+ maxLength: 1024,
14123
+ pattern: "^[^>]+$"
14124
+ }
14125
+ }
14126
+ }
14127
+ ],
14128
+ description: "DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. Supports env/file SecretInput references resolved by configured OpenClaw secret providers."
14129
+ },
14130
+ dmPolicy: {
14131
+ type: "string",
14132
+ enum: ["open", "pairing", "allowlist"],
14133
+ default: "open",
14134
+ description: "Direct-message access policy. open allows any DM, pairing requires host pairing, and allowlist only permits IDs listed in allowFrom."
14135
+ },
14136
+ groupPolicy: {
14137
+ type: "string",
14138
+ enum: ["open", "allowlist", "disabled"],
14139
+ default: "open",
14140
+ description: "Group-message access policy. open allows all groups, allowlist restricts by groupAllowFrom, and disabled blocks group handling."
14141
+ },
14142
+ allowFrom: {
14143
+ type: "array",
14144
+ items: { type: "string" },
14145
+ description: "User IDs allowed when dmPolicy is allowlist. Accepts DingTalk IDs or host-normalized IDs."
14146
+ },
14147
+ groupAllowFrom: {
14148
+ type: "array",
14149
+ items: { type: "string" },
14150
+ description: "Sender IDs allowed when groupPolicy is allowlist. Leave empty unless you want per-sender restrictions in groups."
14151
+ },
14152
+ displayNameResolution: {
14153
+ type: "string",
14154
+ enum: ["disabled", "all"],
14155
+ default: "disabled",
14156
+ 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."
14157
+ },
14158
+ contextVisibility: {
14159
+ type: "string",
14160
+ enum: ["all", "allowlist", "allowlist_quote"],
14161
+ 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."
14162
+ },
14163
+ mediaUrlAllowlist: {
14164
+ type: "array",
14165
+ items: { type: "string" },
14166
+ description: "Allowed download hosts, IPs, or CIDRs for remote media fetches. Use this for internal file servers or custom CDN domains."
14167
+ },
14168
+ ackReaction: {
14169
+ anyOf: [
14170
+ { type: "string", const: "" },
14171
+ { type: "string", enum: ["off", "emoji", "kaomoji"] },
14172
+ { type: "string", minLength: 1 }
14173
+ ],
14174
+ description: "Acknowledgement reaction mode. Supports off, emoji, kaomoji, or a custom compatibility string."
14175
+ },
14176
+ journalTTLDays: {
14177
+ type: "integer",
14178
+ minimum: 1,
14179
+ default: 7,
14180
+ description: "Retention window in days for short-lived message context used by quoting, media recovery, and related reply context features."
14181
+ },
14182
+ debug: {
14183
+ type: "boolean",
14184
+ default: false,
14185
+ description: "Enable verbose DingTalk channel debug logging."
14186
+ },
14187
+ messageType: {
14188
+ type: "string",
14189
+ enum: ["markdown", "card"],
14190
+ default: "markdown",
14191
+ description: "Default reply delivery mode. markdown uses standard messages; card uses DingTalk AI cards when available."
14192
+ },
14193
+ cardTemplateId: {
14194
+ type: "string",
14195
+ description: "Deprecated and ignored. AI card replies now always use the built-in DingTalk template contract; keep only for compatibility with old configs."
14196
+ },
14197
+ cardTemplateKey: {
14198
+ type: "string",
14199
+ default: "content",
14200
+ description: "Deprecated and ignored. The built-in AI card contract owns the streaming field mapping; keep only for compatibility with old configs."
14201
+ },
14202
+ groups: {
14203
+ type: "object",
14204
+ description: 'Per-group overrides keyed by conversationId. Supports "*" as a wildcard fallback.',
14205
+ additionalProperties: {
14206
+ type: "object",
14207
+ additionalProperties: false,
14208
+ properties: {
14209
+ systemPrompt: {
14210
+ type: "string",
14211
+ description: "Additional system prompt appended for this group."
14212
+ },
14213
+ requireMention: {
14214
+ type: "boolean",
14215
+ description: "Require an explicit @mention before the bot answers in this group."
14216
+ },
14217
+ groupAllowFrom: {
14218
+ type: "array",
14219
+ items: { type: "string" },
14220
+ description: "Per-group sender allowlist used when you want tighter access control than the channel default."
14221
+ }
14222
+ }
14223
+ }
14224
+ },
14225
+ maxConnectionAttempts: {
14226
+ type: "integer",
14227
+ minimum: 1,
14228
+ default: 10,
14229
+ description: "Maximum connection attempts in a single reconnect cycle before backing off or giving up."
14230
+ },
14231
+ initialReconnectDelay: {
14232
+ type: "integer",
14233
+ minimum: 100,
14234
+ default: 1e3,
14235
+ description: "Initial reconnect backoff delay in milliseconds."
14236
+ },
14237
+ maxReconnectDelay: {
14238
+ type: "integer",
14239
+ minimum: 1e3,
14240
+ default: 6e4,
14241
+ description: "Upper bound for reconnect backoff delay in milliseconds."
14242
+ },
14243
+ reconnectJitter: {
14244
+ type: "number",
14245
+ minimum: 0,
14246
+ maximum: 1,
14247
+ default: 0.3,
14248
+ description: "Randomization factor added to reconnect backoff to avoid synchronized reconnect storms."
14249
+ },
14250
+ maxReconnectCycles: {
14251
+ type: "integer",
14252
+ minimum: 1,
14253
+ default: 10,
14254
+ description: "Maximum reconnect cycles before the channel stops retrying and waits for the next lifecycle restart."
14255
+ },
14256
+ reconnectDeadlineMs: {
14257
+ type: "integer",
14258
+ minimum: 5e3,
14259
+ default: 5e4,
14260
+ description: "Time limit in milliseconds for one reconnect cycle before starting a fresh cycle."
14261
+ },
14262
+ useConnectionManager: {
14263
+ type: "boolean",
14264
+ default: true,
14265
+ description: "Enable the plugin connection manager. Disable only when you intentionally rely on the DingTalk client keepAlive and autoReconnect behavior."
14266
+ },
14267
+ mediaMaxMb: {
14268
+ type: "integer",
14269
+ minimum: 1,
14270
+ description: "Maximum inbound media size in MB accepted by the plugin. When omitted, the runtime default is used."
14271
+ },
14272
+ keepAlive: {
14273
+ type: "boolean",
14274
+ description: "Enable the underlying Stream client heartbeat. When omitted, runtime derives a default from useConnectionManager."
14275
+ },
14276
+ bypassProxyForSend: {
14277
+ type: "boolean",
14278
+ default: false,
14279
+ description: "Bypass global or system HTTP(S) proxy settings for DingTalk send, upload, and card APIs."
14280
+ },
14281
+ proactivePermissionHint: {
14282
+ type: "object",
14283
+ description: "Controls the proactive-send permission reminder shown when a conversation has not granted send rights yet.",
14284
+ additionalProperties: false,
14285
+ properties: {
14286
+ enabled: {
14287
+ type: "boolean",
14288
+ default: true,
14289
+ description: "Show the proactive-send permission hint when the runtime detects missing DingTalk proactive permission."
14290
+ },
14291
+ cooldownHours: {
14292
+ type: "integer",
14293
+ minimum: 1,
14294
+ maximum: 720,
14295
+ default: 24,
14296
+ description: "Minimum cooldown in hours before the same proactive permission hint can be shown again."
14297
+ }
14298
+ }
14299
+ },
14300
+ cardRealTimeStream: {
14301
+ type: "boolean",
14302
+ default: false,
14303
+ description: 'Deprecated compatibility flag. If true and cardStreamingMode is unset, runtime falls back to cardStreamingMode="all". Do not use in new configs.'
14304
+ },
14305
+ cardStreamingMode: {
14306
+ type: "string",
14307
+ enum: ["off", "answer", "all"],
14308
+ description: "AI card incremental streaming mode. off disables streaming, answer streams answer text only, and all streams answer plus reasoning or thinking text."
14309
+ },
14310
+ cardStreamInterval: {
14311
+ type: "integer",
14312
+ minimum: 200,
14313
+ default: 1e3,
14314
+ description: "Throttle interval in milliseconds between AI card streaming updates."
14315
+ },
14316
+ aicardDegradeMs: {
14317
+ type: "integer",
14318
+ minimum: 6e4,
14319
+ default: 18e5,
14320
+ description: "Cooldown window in milliseconds after AI card trigger errors. During this window replies fall back to non-card delivery."
14321
+ },
14322
+ learningEnabled: {
14323
+ type: "boolean",
14324
+ description: "Enable the local feedback-learning loop for notes, reflections, and command-assisted learning."
14325
+ },
14326
+ learningAutoApply: {
14327
+ type: "boolean",
14328
+ description: "Automatically apply generated learning output into session notes or global rules when available."
14329
+ },
14330
+ learningNoteTtlMs: {
14331
+ type: "integer",
14332
+ minimum: 6e4,
14333
+ description: "Retention window in milliseconds for temporary learning notes."
14334
+ },
14335
+ convertMarkdownTables: {
14336
+ type: "boolean",
14337
+ default: true,
14338
+ description: "Convert markdown tables to plain text before sending when you want more consistent DingTalk rendering."
14339
+ },
14340
+ cardAtSender: {
14341
+ type: "string",
14342
+ description: "Send a follow-up @mention text after card finalization in group chats. Any non-empty string becomes the message body."
14343
+ },
14344
+ cardStatusLine: {
14345
+ type: "object",
14346
+ description: "Status line visibility toggles for the AI card footer.",
14347
+ additionalProperties: false,
14348
+ default: { model: true, effort: true, agent: true, taskTime: false, tokens: false, dapiUsage: false },
14349
+ properties: {
14350
+ model: {
14351
+ type: "boolean",
14352
+ default: true,
14353
+ description: "Show model name in AI card status line footer."
14354
+ },
14355
+ effort: {
14356
+ type: "boolean",
14357
+ default: true,
14358
+ description: "Show thinking effort level in AI card status line footer."
14359
+ },
14360
+ agent: {
14361
+ type: "boolean",
14362
+ default: true,
14363
+ description: "Show agent display name in AI card status line footer."
14364
+ },
14365
+ taskTime: {
14366
+ type: "boolean",
14367
+ default: false,
14368
+ description: "Show task elapsed time in AI card status line footer."
14369
+ },
14370
+ tokens: {
14371
+ type: "boolean",
14372
+ default: false,
14373
+ description: "Show token usage summary (input/output/cache) in AI card status line footer."
14374
+ },
14375
+ dapiUsage: {
14376
+ type: "boolean",
14377
+ default: false,
14378
+ description: "Show DingTalk API call count in AI card status line footer."
14379
+ }
14380
+ }
14381
+ }
14382
+ }
14383
+ }
14384
+ }
14385
+ }
14386
+ },
14387
+ uiHints: {
14388
+ "": {
14389
+ label: "DingTalk",
14390
+ help: "DingTalk Stream-mode channel configuration including auth, delivery, targeting, and AI Card behavior."
14391
+ },
14392
+ name: {
14393
+ label: "Display Name",
14394
+ help: "Optional display name for the default DingTalk account shown in host configuration surfaces."
14395
+ },
14396
+ enabled: {
14397
+ label: "Enabled",
14398
+ help: "Turn the DingTalk channel on or off without deleting the saved configuration."
14399
+ },
14400
+ agentId: {
14401
+ label: "Legacy Agent ID",
14402
+ help: "Deprecated compatibility field from older setup flows. The current runtime ignores it, so leave it empty in new configs."
14403
+ },
14404
+ corpId: {
14405
+ label: "Legacy Corp ID",
14406
+ help: "Deprecated compatibility field from older enterprise app setup flows. Stream mode no longer needs or reads it."
14407
+ },
14408
+ clientId: {
14409
+ label: "DingTalk Client ID",
14410
+ help: "App Key used to authenticate DingTalk API requests."
14411
+ },
14412
+ clientSecret: {
14413
+ label: "DingTalk Client Secret",
14414
+ help: "App Secret used to obtain DingTalk access tokens. SecretInput references are resolved by configured OpenClaw secret providers.",
14415
+ sensitive: true
14416
+ },
14417
+ dmPolicy: {
14418
+ label: "Direct Message Policy",
14419
+ help: "Choose who can talk to the bot in direct messages: open, host pairing only, or explicit allowlist."
14420
+ },
14421
+ groupPolicy: {
14422
+ label: "Group Message Policy",
14423
+ help: "Choose whether group messages are open to all groups, restricted by allowlist, or completely disabled."
14424
+ },
14425
+ allowFrom: {
14426
+ label: "DM Allowlist",
14427
+ help: "User IDs allowed when direct message policy is set to allowlist."
14428
+ },
14429
+ groupAllowFrom: {
14430
+ label: "Group Sender Allowlist",
14431
+ help: "Sender IDs allowed when group policy is allowlist or when you want tighter group sender restrictions."
14432
+ },
14433
+ messageType: {
14434
+ label: "Reply Message Type",
14435
+ help: "Choose markdown for standard replies or card for AI Card delivery."
14436
+ },
14437
+ ackReaction: {
14438
+ label: "Ack Reaction",
14439
+ help: "Native acknowledgement reaction mode. Supports preset values or a custom string for backward compatibility."
14440
+ },
14441
+ displayNameResolution: {
14442
+ label: "Display Name Resolution",
14443
+ help: "Keep disabled unless you explicitly want learned display-name targeting for all callers."
14444
+ },
14445
+ contextVisibility: {
14446
+ label: "Context Visibility",
14447
+ help: "Host-level context filtering. allowlist_quote is the safest advanced mode when you only want explicit quote/reply context."
14448
+ },
14449
+ mediaUrlAllowlist: {
14450
+ label: "Media URL Allowlist",
14451
+ help: "Allow remote media downloads only from the specified hosts, IPs, or CIDR ranges."
14452
+ },
14453
+ journalTTLDays: {
14454
+ label: "Message Context TTL (days)",
14455
+ help: "Retention period for short-lived quoted message and media recovery context."
14456
+ },
14457
+ debug: {
14458
+ label: "Debug Logging",
14459
+ help: "Enable verbose DingTalk channel debug logs for diagnostics."
14460
+ },
14461
+ cardTemplateId: {
14462
+ label: "AI Card Template ID",
14463
+ help: "Deprecated and ignored. AI card replies now always use the built-in DingTalk template contract."
14464
+ },
14465
+ cardTemplateKey: {
14466
+ label: "AI Card Template Field Key",
14467
+ help: "Deprecated and ignored. The built-in AI card contract owns the streaming field mapping."
14468
+ },
14469
+ groups: {
14470
+ label: "Per-Group Overrides",
14471
+ help: "Configure conversationId-specific overrides, or use * as a wildcard fallback."
14472
+ },
14473
+ "groups.systemPrompt": {
14474
+ label: "Group System Prompt",
14475
+ help: "Extra system prompt appended only for the selected group."
14476
+ },
14477
+ "groups.requireMention": {
14478
+ label: "Require Mention",
14479
+ help: "Require an explicit @mention before the bot answers in that group."
14480
+ },
14481
+ "groups.groupAllowFrom": {
14482
+ label: "Per-Group Sender Allowlist",
14483
+ help: "Optional sender allowlist that overrides or tightens the channel-level group access rule."
14484
+ },
14485
+ maxConnectionAttempts: {
14486
+ label: "Max Connection Attempts",
14487
+ help: "How many connection attempts are allowed within a reconnect cycle before the cycle backs off or ends."
14488
+ },
14489
+ initialReconnectDelay: {
14490
+ label: "Initial Reconnect Delay",
14491
+ help: "Initial reconnect backoff delay in milliseconds."
14492
+ },
14493
+ maxReconnectDelay: {
14494
+ label: "Max Reconnect Delay",
14495
+ help: "Upper bound for reconnect backoff delay in milliseconds."
14496
+ },
14497
+ reconnectJitter: {
14498
+ label: "Reconnect Jitter",
14499
+ help: "Randomization factor added to reconnect backoff to reduce synchronized reconnect storms."
14500
+ },
14501
+ maxReconnectCycles: {
14502
+ label: "Max Reconnect Cycles",
14503
+ help: "Maximum reconnect cycles before the channel stops retrying and waits for a later restart."
14504
+ },
14505
+ reconnectDeadlineMs: {
14506
+ label: "Reconnect Cycle Deadline",
14507
+ help: "Time limit in milliseconds for one reconnect cycle before starting a fresh cycle."
14508
+ },
14509
+ useConnectionManager: {
14510
+ label: "Use Connection Manager",
14511
+ help: "Recommended default. Disable only when you intentionally rely on the DingTalk client keepAlive and autoReconnect behavior."
14512
+ },
14513
+ mediaMaxMb: {
14514
+ label: "Max Media Size (MB)",
14515
+ help: "Maximum inbound media size accepted by the plugin. Leave empty to use the runtime default."
14516
+ },
14517
+ keepAlive: {
14518
+ label: "Stream KeepAlive",
14519
+ help: "Optional explicit heartbeat toggle for the underlying Stream client."
14520
+ },
14521
+ bypassProxyForSend: {
14522
+ label: "Bypass Proxy For Send",
14523
+ help: "Skip global or system HTTP(S) proxy settings for DingTalk send, upload, and card APIs."
14524
+ },
14525
+ cardRealTimeStream: {
14526
+ label: "Legacy Real-Time Card Stream",
14527
+ help: "Deprecated compatibility flag. When true and cardStreamingMode is unset, runtime falls back to all-streaming. Do not use in new configs."
14528
+ },
14529
+ cardStreamingMode: {
14530
+ label: "Card Streaming Mode",
14531
+ help: "Choose off, answer, or all. answer streams only answer text, while all streams answer plus reasoning or thinking text."
14532
+ },
14533
+ cardStreamInterval: {
14534
+ label: "Card Stream Interval (ms)",
14535
+ help: "Throttle interval in milliseconds between AI card incremental updates."
14536
+ },
14537
+ aicardDegradeMs: {
14538
+ label: "AI Card Degrade Window (ms)",
14539
+ help: "Cooldown window after AI card trigger errors. Replies fall back to non-card delivery during this period."
14540
+ },
14541
+ learningEnabled: {
14542
+ label: "Learning Enabled",
14543
+ help: "Enable the local feedback-learning loop for notes, reflections, and learning commands."
14544
+ },
14545
+ learningAutoApply: {
14546
+ label: "Learning Auto Apply",
14547
+ help: "Automatically apply generated learning output into session notes or global rules when available."
14548
+ },
14549
+ learningNoteTtlMs: {
14550
+ label: "Learning Note TTL (ms)",
14551
+ help: "Retention period for temporary learning notes."
14552
+ },
14553
+ convertMarkdownTables: {
14554
+ label: "Convert Markdown Tables",
14555
+ help: "Flatten markdown tables into plain text before sending when you want more consistent DingTalk rendering."
14556
+ },
14557
+ cardAtSender: {
14558
+ label: "Card Mention Follow-Up",
14559
+ help: "Send a follow-up @mention text after card finalization in group chats. Any non-empty string becomes the message body."
14560
+ },
14561
+ cardStatusLine: {
14562
+ label: "Status Line",
14563
+ help: "Configure which information segments appear in the AI card footer status line."
14564
+ },
14565
+ "cardStatusLine.model": {
14566
+ label: "Model Name",
14567
+ help: "Show the LLM model name in the AI card footer status line."
14568
+ },
14569
+ "cardStatusLine.effort": {
14570
+ label: "Thinking Effort",
14571
+ help: "Show the thinking effort level (e.g. high, medium) in the AI card footer."
14572
+ },
14573
+ "cardStatusLine.agent": {
14574
+ label: "Agent Name",
14575
+ help: "Show the agent display name in the AI card footer."
14576
+ },
14577
+ "cardStatusLine.taskTime": {
14578
+ label: "Task Time",
14579
+ help: "Show elapsed task time in the AI card footer."
14580
+ },
14581
+ "cardStatusLine.tokens": {
14582
+ label: "Token Usage",
14583
+ help: "Show a compact token usage summary (input/output/cache) in the AI card footer."
14584
+ },
14585
+ "cardStatusLine.dapiUsage": {
14586
+ label: "API Calls",
14587
+ help: "Show the DingTalk API call count in the AI card footer."
14588
+ },
14589
+ showThinkingStream: {
14590
+ label: "Legacy Show Thinking Stream",
14591
+ help: "Deprecated compatibility field from older card streaming behavior. The current runtime ignores it."
14592
+ },
14593
+ asyncMode: {
14594
+ label: "Legacy Async Mode",
14595
+ help: "Deprecated compatibility field from older host or runtime integrations. The current runtime ignores it."
14596
+ },
14597
+ accounts: {
14598
+ label: "Named Accounts",
14599
+ help: "Create named DingTalk accounts when one plugin instance must serve multiple apps or tenants."
14600
+ },
14601
+ "proactivePermissionHint.enabled": {
14602
+ label: "Proactive Permission Hint Enabled",
14603
+ help: "Show a reminder when DingTalk proactive-send permission has not been granted."
14604
+ },
14605
+ "proactivePermissionHint.cooldownHours": {
14606
+ label: "Proactive Hint Cooldown (hours)",
14607
+ help: "Minimum cooldown before the same proactive permission hint can be shown again."
14608
+ }
14609
+ }
14610
+ }
14611
+ }
13953
14612
  };
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
14613
 
13960
14614
  // src/gateway/channel-gateway.ts
13961
14615
  import { DWClient, TOPIC_CARD, TOPIC_ROBOT } from "dingtalk-stream";
14616
+ import { listAgentIds } from "openclaw/plugin-sdk/agent-runtime";
13962
14617
 
13963
14618
  // src/card/card-stop-handler.ts
13964
14619
  async function stopCardRun(params) {
@@ -14929,11 +15584,69 @@ function createDingTalkGateway() {
14929
15584
  debug: config.debug,
14930
15585
  baseLog: ctx.log
14931
15586
  });
15587
+ try {
15588
+ const knownGroupIds = /* @__PURE__ */ new Set();
15589
+ for (const group of listKnownGroupTargets({
15590
+ storePath: accountStorePath,
15591
+ accountId: account.accountId
15592
+ })) {
15593
+ registerPeerId(group.conversationId);
15594
+ knownGroupIds.add(group.conversationId.toLowerCase());
15595
+ }
15596
+ const runtime = getDingTalkRuntime();
15597
+ for (const agentId of listAgentIds(cfg)) {
15598
+ const sessionStorePath = runtime.channel.session.resolveStorePath(cfg.session?.store, {
15599
+ agentId
15600
+ });
15601
+ for (const { entry } of runtime.agent.session.listSessionEntries({
15602
+ agentId,
15603
+ storePath: sessionStorePath,
15604
+ readConsistency: "latest"
15605
+ })) {
15606
+ const isDingTalkSession = [
15607
+ entry.lastChannel,
15608
+ entry.channel,
15609
+ entry.origin?.provider,
15610
+ entry.origin?.surface
15611
+ ].some((value) => value === "dingtalk");
15612
+ const sessionAccountId = entry.lastAccountId || entry.origin?.accountId;
15613
+ if (!isDingTalkSession || sessionAccountId !== account.accountId) {
15614
+ continue;
15615
+ }
15616
+ for (const peerId of [entry.lastTo, entry.origin?.from, entry.origin?.to]) {
15617
+ if (typeof peerId !== "string" || !peerId.startsWith("cid")) {
15618
+ continue;
15619
+ }
15620
+ registerPeerId(peerId);
15621
+ const normalizedPeerId = peerId.toLowerCase();
15622
+ if (!knownGroupIds.has(normalizedPeerId)) {
15623
+ upsertObservedGroupTarget({
15624
+ storePath: accountStorePath,
15625
+ accountId: account.accountId,
15626
+ conversationId: peerId,
15627
+ seenAt: entry.updatedAt
15628
+ });
15629
+ knownGroupIds.add(normalizedPeerId);
15630
+ }
15631
+ }
15632
+ }
15633
+ }
15634
+ for (const user of listKnownUserTargets({
15635
+ storePath: accountStorePath,
15636
+ accountId: account.accountId
15637
+ })) {
15638
+ registerPeerId(user.canonicalUserId);
15639
+ registerPeerId(user.staffId || "");
15640
+ registerPeerId(user.senderId);
15641
+ }
15642
+ } catch (err) {
15643
+ pluginLog?.warn?.(
15644
+ `[${account.accountId}] Failed to restore peer IDs: ${err instanceof Error ? err.message : String(err)}`
15645
+ );
15646
+ }
14932
15647
  const runtimeConfig = await resolveRuntimeConfig(config, pluginLog);
14933
15648
  setCurrentLogger(pluginLog, account.accountId);
14934
15649
  pluginLog?.info?.(`[${account.accountId}] Initializing DingTalk Stream client...`);
14935
- preloadPeerIdsFromSessions();
14936
- pluginLog?.debug?.(`[${account.accountId}] Peer ID registry preloaded from sessions`);
14937
15650
  cleanupOrphanedTempFiles(pluginLog);
14938
15651
  try {
14939
15652
  const recovered = await recoverPendingCardsForAccount(
@@ -16013,10 +16726,9 @@ function describeDingTalkMessageTool(cfg) {
16013
16726
  if (!configured && !(config.accounts && Object.keys(config.accounts).length > 0)) {
16014
16727
  return { actions: [], capabilities: [], schema: null };
16015
16728
  }
16016
- const hasCardMode = config.messageType === "card" || config.accounts && Object.values(config.accounts).some((account) => account?.messageType === "card");
16017
16729
  return {
16018
16730
  actions: ["send"],
16019
- capabilities: hasCardMode ? ["cards"] : [],
16731
+ capabilities: [],
16020
16732
  schema: null
16021
16733
  };
16022
16734
  }
@@ -16485,7 +17197,7 @@ var dingtalkPlugin = {
16485
17197
  blurb: "\u9489\u9489\u4F01\u4E1A\u5185\u90E8\u673A\u5668\u4EBA\uFF0C\u4F7F\u7528 Stream \u6A21\u5F0F\uFF0C\u65E0\u9700\u516C\u7F51 IP\u3002",
16486
17198
  aliases: ["dd", "ding"]
16487
17199
  },
16488
- configSchema: buildChannelConfigSchema(DingTalkConfigSchema),
17200
+ configSchema: buildJsonChannelConfigSchema(openclaw_plugin_default.channelConfigs.dingtalk.schema),
16489
17201
  setup: dingtalkSetupAdapter,
16490
17202
  setupWizard: dingtalkSetupWizard,
16491
17203
  capabilities: {
@@ -16936,7 +17648,7 @@ var dingtalkEntry = defineChannelPluginEntry({
16936
17648
  );
16937
17649
  }
16938
17650
  });
16939
- var index_default = {
17651
+ var pluginDefinition = {
16940
17652
  ...dingtalkEntry,
16941
17653
  register(api) {
16942
17654
  const registrationMode = api.registrationMode;
@@ -16950,9 +17662,9 @@ var index_default = {
16950
17662
  dingtalkEntry.register(api);
16951
17663
  }
16952
17664
  };
17665
+ var index_default = pluginDefinition;
16953
17666
  export {
16954
17667
  index_default as default,
16955
17668
  dingtalkPlugin,
16956
17669
  setDingTalkRuntime
16957
17670
  };
16958
- //# sourceMappingURL=index.js.map