@soimy/dingtalk 3.5.2 → 3.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -23
- package/openclaw.plugin.json +695 -0
- package/package.json +5 -5
- package/src/card/card-streaming-mode.ts +30 -0
- package/src/card/reasoning-answer-split.ts +162 -0
- package/src/card-draft-controller.ts +85 -6
- package/src/card-service.ts +111 -0
- package/src/channel.ts +60 -41
- package/src/config-schema.ts +62 -38
- package/src/config.ts +25 -3
- package/src/inbound-handler.ts +355 -38
- package/src/media-utils.ts +163 -7
- package/src/message-utils.ts +33 -5
- package/src/messaging/quoted-file-service.ts +9 -4
- package/src/onboarding.ts +29 -0
- package/src/plugin-sdk-channel-actions-augment.ts +11 -0
- package/src/reply-strategy-card.ts +247 -32
- package/src/reply-strategy-markdown.ts +1 -1
- package/src/reply-strategy.ts +18 -2
- package/src/send-service.ts +110 -4
- package/src/targeting/agent-routing.ts +44 -28
- package/src/types.ts +60 -4
- package/src/utils.ts +25 -0
package/src/channel.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { DWClient, TOPIC_CARD, TOPIC_ROBOT } from "dingtalk-stream";
|
|
3
|
+
import { jsonResult } from "openclaw/plugin-sdk/channel-actions";
|
|
3
4
|
import type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-contract";
|
|
4
5
|
import { buildChannelConfigSchema, type OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
5
|
-
import { jsonResult } from "openclaw/plugin-sdk/telegram-core";
|
|
6
6
|
import { readStringParam } from "openclaw/plugin-sdk/param-readers";
|
|
7
7
|
import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
|
|
8
8
|
import { getAccessToken } from "./auth";
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
formatDingTalkConnectionErrorLog,
|
|
69
69
|
formatDingTalkErrorPayloadLog,
|
|
70
70
|
getCurrentTimestamp,
|
|
71
|
+
parseBooleanLike,
|
|
71
72
|
resolvePluginDebugLog,
|
|
72
73
|
} from "./utils";
|
|
73
74
|
|
|
@@ -188,29 +189,15 @@ function logInboundCounters(log: any, accountId: string, reason: string): void {
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
function readBooleanLikeParam(params: Record<string, unknown>, key: string): boolean | undefined {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
if (value === 0) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
if (typeof value === "string") {
|
|
205
|
-
const normalized = value.trim().toLowerCase();
|
|
206
|
-
if (["1", "true", "yes", "y", "on"].includes(normalized)) {
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
if (["0", "false", "no", "n", "off"].includes(normalized)) {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
192
|
+
return parseBooleanLike(params[key]);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function readSharedAudioAsVoiceParam(params: Record<string, unknown>): boolean {
|
|
196
|
+
const sharedValue = readBooleanLikeParam(params, "audioAsVoice");
|
|
197
|
+
if (sharedValue !== undefined) {
|
|
198
|
+
return sharedValue;
|
|
212
199
|
}
|
|
213
|
-
return
|
|
200
|
+
return readBooleanLikeParam(params, "asVoice") === true;
|
|
214
201
|
}
|
|
215
202
|
|
|
216
203
|
function describeDingTalkMessageTool(cfg: OpenClawConfig): {
|
|
@@ -261,7 +248,7 @@ const dingtalkMessageActions: ChannelMessageActionAdapter = {
|
|
|
261
248
|
message = caption;
|
|
262
249
|
}
|
|
263
250
|
|
|
264
|
-
const asVoice =
|
|
251
|
+
const asVoice = readSharedAudioAsVoiceParam(params);
|
|
265
252
|
const requestedMediaType = readStringParam(params, "mediaType");
|
|
266
253
|
|
|
267
254
|
const target = resolveOriginalPeerId(stripTargetPrefix(to).targetId);
|
|
@@ -507,6 +494,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
507
494
|
filePath,
|
|
508
495
|
mediaUrl,
|
|
509
496
|
mediaType: providedMediaType,
|
|
497
|
+
audioAsVoice,
|
|
510
498
|
asVoice,
|
|
511
499
|
accountId,
|
|
512
500
|
mediaLocalRoots,
|
|
@@ -570,7 +558,7 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
570
558
|
const mediaType = resolveOutboundMediaType({
|
|
571
559
|
mediaType: typeof providedMediaType === "string" ? providedMediaType : undefined,
|
|
572
560
|
mediaPath: actualMediaPath,
|
|
573
|
-
asVoice: asVoice
|
|
561
|
+
asVoice: readSharedAudioAsVoiceParam({ audioAsVoice, asVoice }),
|
|
574
562
|
});
|
|
575
563
|
let result;
|
|
576
564
|
try {
|
|
@@ -679,6 +667,12 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
679
667
|
}
|
|
680
668
|
|
|
681
669
|
const useConnectionManager = config.useConnectionManager ?? true;
|
|
670
|
+
const applyStatusPatch = (patch: Record<string, unknown>) => {
|
|
671
|
+
ctx.setStatus({
|
|
672
|
+
...ctx.getStatus(),
|
|
673
|
+
...patch,
|
|
674
|
+
});
|
|
675
|
+
};
|
|
682
676
|
|
|
683
677
|
// Factory that creates a fresh DWClient with the TOPIC_ROBOT callback
|
|
684
678
|
// already registered. Each client captures its own reference for
|
|
@@ -721,6 +715,14 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
721
715
|
};
|
|
722
716
|
try {
|
|
723
717
|
const data = JSON.parse(res.data) as DingTalkInboundMessage;
|
|
718
|
+
// Record the latest inbound callback arrival for status/UI projection.
|
|
719
|
+
// This intentionally tracks "message reached the plugin callback" rather
|
|
720
|
+
// than "message passed dedup and completed processing".
|
|
721
|
+
applyStatusPatch({
|
|
722
|
+
connected: true,
|
|
723
|
+
lastInboundAt: getCurrentTimestamp(),
|
|
724
|
+
lastEventAt: getCurrentTimestamp(),
|
|
725
|
+
});
|
|
724
726
|
|
|
725
727
|
const robotKey = resolveRobotCode(config) || account.accountId;
|
|
726
728
|
const msgId = data.msgId || messageId;
|
|
@@ -912,9 +914,10 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
912
914
|
nativeStopResolve?.();
|
|
913
915
|
}
|
|
914
916
|
|
|
915
|
-
|
|
916
|
-
...ctx.getStatus(),
|
|
917
|
+
applyStatusPatch({
|
|
917
918
|
running: false,
|
|
919
|
+
connected: false,
|
|
920
|
+
lastEventAt: getCurrentTimestamp(),
|
|
918
921
|
lastStopAt: getCurrentTimestamp(),
|
|
919
922
|
});
|
|
920
923
|
|
|
@@ -931,9 +934,10 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
931
934
|
`[${account.accountId}] Abort signal already active, skipping connection`,
|
|
932
935
|
);
|
|
933
936
|
|
|
934
|
-
|
|
935
|
-
...ctx.getStatus(),
|
|
937
|
+
applyStatusPatch({
|
|
936
938
|
running: false,
|
|
939
|
+
connected: false,
|
|
940
|
+
lastEventAt: getCurrentTimestamp(),
|
|
937
941
|
lastStopAt: getCurrentTimestamp(),
|
|
938
942
|
lastError: "Connection aborted before start",
|
|
939
943
|
});
|
|
@@ -956,9 +960,11 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
956
960
|
try {
|
|
957
961
|
await client.connect();
|
|
958
962
|
if (!stopped) {
|
|
959
|
-
|
|
960
|
-
...ctx.getStatus(),
|
|
963
|
+
applyStatusPatch({
|
|
961
964
|
running: true,
|
|
965
|
+
connected: true,
|
|
966
|
+
lastConnectedAt: getCurrentTimestamp(),
|
|
967
|
+
lastEventAt: getCurrentTimestamp(),
|
|
962
968
|
lastStartAt: getCurrentTimestamp(),
|
|
963
969
|
lastError: null,
|
|
964
970
|
});
|
|
@@ -974,9 +980,10 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
974
980
|
`[${account.accountId}] Failed to establish connection: ${err.message}`,
|
|
975
981
|
) ?? `[${account.accountId}] Failed to establish connection: ${err.message}`,
|
|
976
982
|
);
|
|
977
|
-
|
|
978
|
-
...ctx.getStatus(),
|
|
983
|
+
applyStatusPatch({
|
|
979
984
|
running: false,
|
|
985
|
+
connected: false,
|
|
986
|
+
lastEventAt: getCurrentTimestamp(),
|
|
980
987
|
lastError: err.message || "Connection failed",
|
|
981
988
|
});
|
|
982
989
|
throw err;
|
|
@@ -1004,9 +1011,11 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
1004
1011
|
`[${account.accountId}] Connection state changed to: ${state}${error ? ` (${error})` : ""}`,
|
|
1005
1012
|
);
|
|
1006
1013
|
if (state === ConnectionState.CONNECTED) {
|
|
1007
|
-
|
|
1008
|
-
...ctx.getStatus(),
|
|
1014
|
+
applyStatusPatch({
|
|
1009
1015
|
running: true,
|
|
1016
|
+
connected: true,
|
|
1017
|
+
lastConnectedAt: getCurrentTimestamp(),
|
|
1018
|
+
lastEventAt: getCurrentTimestamp(),
|
|
1010
1019
|
lastStartAt: getCurrentTimestamp(),
|
|
1011
1020
|
lastError: null,
|
|
1012
1021
|
});
|
|
@@ -1027,9 +1036,10 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
1027
1036
|
`[${account.accountId}] Cleared ${cleared} stale in-flight lock(s) on disconnect`,
|
|
1028
1037
|
);
|
|
1029
1038
|
}
|
|
1030
|
-
|
|
1031
|
-
...ctx.getStatus(),
|
|
1039
|
+
applyStatusPatch({
|
|
1032
1040
|
running: false,
|
|
1041
|
+
connected: false,
|
|
1042
|
+
lastEventAt: getCurrentTimestamp(),
|
|
1033
1043
|
lastError: error || `Connection ${state.toLowerCase()}`,
|
|
1034
1044
|
});
|
|
1035
1045
|
}
|
|
@@ -1054,9 +1064,11 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
1054
1064
|
await connectionManager.connect();
|
|
1055
1065
|
|
|
1056
1066
|
if (!stopped && connectionManager.isConnected()) {
|
|
1057
|
-
|
|
1058
|
-
...ctx.getStatus(),
|
|
1067
|
+
applyStatusPatch({
|
|
1059
1068
|
running: true,
|
|
1069
|
+
connected: true,
|
|
1070
|
+
lastConnectedAt: getCurrentTimestamp(),
|
|
1071
|
+
lastEventAt: getCurrentTimestamp(),
|
|
1060
1072
|
lastStartAt: getCurrentTimestamp(),
|
|
1061
1073
|
lastError: null,
|
|
1062
1074
|
});
|
|
@@ -1079,9 +1091,10 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
1079
1091
|
) ?? `[${account.accountId}] Failed to establish connection: ${err.message}`,
|
|
1080
1092
|
);
|
|
1081
1093
|
|
|
1082
|
-
|
|
1083
|
-
...ctx.getStatus(),
|
|
1094
|
+
applyStatusPatch({
|
|
1084
1095
|
running: false,
|
|
1096
|
+
connected: false,
|
|
1097
|
+
lastEventAt: getCurrentTimestamp(),
|
|
1085
1098
|
lastError: err.message || "Connection failed",
|
|
1086
1099
|
});
|
|
1087
1100
|
throw err;
|
|
@@ -1098,7 +1111,10 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
1098
1111
|
defaultRuntime: {
|
|
1099
1112
|
accountId: "default",
|
|
1100
1113
|
running: false,
|
|
1114
|
+
connected: false,
|
|
1101
1115
|
lastEventAt: null,
|
|
1116
|
+
lastConnectedAt: null,
|
|
1117
|
+
lastInboundAt: null,
|
|
1102
1118
|
lastStartAt: null,
|
|
1103
1119
|
lastStopAt: null,
|
|
1104
1120
|
lastError: null,
|
|
@@ -1155,7 +1171,10 @@ export const dingtalkPlugin: DingTalkChannelPlugin = {
|
|
|
1155
1171
|
configured: account.configured,
|
|
1156
1172
|
clientId: account.config?.clientId ?? null,
|
|
1157
1173
|
running,
|
|
1174
|
+
connected: runtime?.connected ?? snapshot?.connected ?? null,
|
|
1158
1175
|
lastEventAt: running ? getCurrentTimestamp() : persistedLastEventAt,
|
|
1176
|
+
lastConnectedAt: runtime?.lastConnectedAt ?? snapshot?.lastConnectedAt ?? null,
|
|
1177
|
+
lastInboundAt: runtime?.lastInboundAt ?? snapshot?.lastInboundAt ?? null,
|
|
1159
1178
|
lastStartAt: runtime?.lastStartAt ?? snapshot?.lastStartAt ?? null,
|
|
1160
1179
|
lastStopAt: runtime?.lastStopAt ?? snapshot?.lastStopAt ?? null,
|
|
1161
1180
|
lastError: runtime?.lastError ?? snapshot?.lastError ?? null,
|
package/src/config-schema.ts
CHANGED
|
@@ -7,65 +7,78 @@ const AckReactionSchema = z.union([
|
|
|
7
7
|
z.string().min(1),
|
|
8
8
|
]);
|
|
9
9
|
|
|
10
|
+
const CardStreamingModeSchema = z.enum(["off", "answer", "all"]);
|
|
11
|
+
const ContextVisibilitySchema = z.enum(["all", "allowlist", "allowlist_quote"]);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Runtime-parsed DingTalk account config.
|
|
15
|
+
*
|
|
16
|
+
* Compatibility note:
|
|
17
|
+
* - `agentId`, `corpId`, `showThinkingStream`, and `asyncMode` are intentionally
|
|
18
|
+
* not parsed here. They remain only in manifest metadata for legacy host/UI
|
|
19
|
+
* compatibility and are ignored by the current runtime.
|
|
20
|
+
*/
|
|
10
21
|
const DingTalkAccountConfigShape = {
|
|
11
22
|
/** Account name (optional display name) */
|
|
12
23
|
name: z.string().optional(),
|
|
13
24
|
|
|
14
|
-
/**
|
|
25
|
+
/** Enable or disable this DingTalk channel/account without deleting saved credentials. */
|
|
15
26
|
enabled: z.boolean().optional().default(true),
|
|
16
27
|
|
|
17
|
-
/** DingTalk App Key (Client ID)
|
|
28
|
+
/** DingTalk App Key (Client ID) used to authenticate API and Stream connections. */
|
|
18
29
|
clientId: z.string().optional(),
|
|
19
30
|
|
|
20
|
-
/** DingTalk App Secret (Client Secret)
|
|
31
|
+
/** DingTalk App Secret (Client Secret) used to obtain DingTalk access tokens. */
|
|
21
32
|
clientSecret: z.string().optional(),
|
|
22
33
|
|
|
23
|
-
/** Direct
|
|
34
|
+
/** Direct-message access policy: open, pairing, or allowlist. */
|
|
24
35
|
dmPolicy: z.enum(["open", "pairing", "allowlist"]).optional().default("open"),
|
|
25
36
|
|
|
26
|
-
/** Group
|
|
37
|
+
/** Group-message access policy: open, allowlist, or disabled. */
|
|
27
38
|
groupPolicy: z.enum(["open", "allowlist", "disabled"]).optional().default("open"),
|
|
28
39
|
|
|
29
|
-
/**
|
|
40
|
+
/** User IDs allowed when `dmPolicy` is `allowlist`. */
|
|
30
41
|
allowFrom: z.array(z.string()).optional(),
|
|
31
42
|
|
|
32
|
-
/**
|
|
43
|
+
/** Sender IDs allowed when `groupPolicy` is `allowlist`. */
|
|
33
44
|
groupAllowFrom: z.array(z.string()).optional(),
|
|
34
45
|
|
|
35
|
-
/** Default disabled. Enabling
|
|
46
|
+
/** 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. */
|
|
36
47
|
displayNameResolution: z.enum(["disabled", "all"]).optional().default("disabled"),
|
|
37
48
|
|
|
49
|
+
/** 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. */
|
|
50
|
+
contextVisibility: ContextVisibilitySchema.optional(),
|
|
51
|
+
|
|
52
|
+
/** Allowed remote media download hosts, IPs, or CIDRs for media fetches. */
|
|
38
53
|
mediaUrlAllowlist: z.array(z.string()).optional(),
|
|
39
54
|
|
|
40
|
-
/** Native
|
|
55
|
+
/** Native acknowledgement reaction mode: off, emoji, kaomoji, or a custom compatibility string. */
|
|
41
56
|
ackReaction: AckReactionSchema.optional(),
|
|
42
57
|
|
|
58
|
+
/** Retention window in days for short-lived message context used by quoting and media recovery. */
|
|
43
59
|
journalTTLDays: z.number().int().min(1).optional().default(DEFAULT_MESSAGE_CONTEXT_TTL_DAYS),
|
|
44
|
-
/** Enable debug logging */
|
|
60
|
+
/** Enable verbose DingTalk channel debug logging. */
|
|
45
61
|
debug: z.boolean().optional().default(false),
|
|
46
62
|
|
|
47
|
-
/**
|
|
63
|
+
/** Default reply delivery mode: markdown or card. */
|
|
48
64
|
messageType: z.enum(["markdown", "card"]).optional().default("markdown"),
|
|
49
65
|
|
|
50
|
-
/**
|
|
51
|
-
* obtain the template ID from DingTalk Developer Console.
|
|
52
|
-
* ref: https://github.com/soimy/openclaw-channel-dingtalk/blob/main/README.md#3-%E5%BB%BA%E7%AB%8B%E5%8D%A1%E7%89%87%E6%A8%A1%E6%9D%BF%E5%8F%AF%E9%80%89
|
|
53
|
-
*/
|
|
66
|
+
/** Deprecated and ignored. AI card replies now always use the built-in DingTalk template contract. Keep only for backward-compatible config parsing. */
|
|
54
67
|
cardTemplateId: z.string().optional(),
|
|
55
68
|
|
|
56
|
-
/**
|
|
57
|
-
* Default: 'content' - maps to the content field in the card template
|
|
58
|
-
* This key is used in the streaming API to update specific fields in the card.
|
|
59
|
-
*/
|
|
69
|
+
/** Deprecated and ignored. The built-in AI card contract owns the streaming field mapping. Keep only for backward-compatible config parsing. */
|
|
60
70
|
cardTemplateKey: z.string().optional().default("content"),
|
|
61
71
|
|
|
62
|
-
/** Per-group
|
|
72
|
+
/** Per-group overrides keyed by conversationId. Supports `*` as a wildcard fallback. */
|
|
63
73
|
groups: z
|
|
64
74
|
.record(
|
|
65
75
|
z.string(),
|
|
66
76
|
z.object({
|
|
77
|
+
/** Additional system prompt appended for this group. */
|
|
67
78
|
systemPrompt: z.string().optional(),
|
|
79
|
+
/** Require an explicit @mention before the bot answers in this group. */
|
|
68
80
|
requireMention: z.boolean().optional(),
|
|
81
|
+
/** Optional per-group sender allowlist for tighter access control than the channel default. */
|
|
69
82
|
groupAllowFrom: z.array(z.string()).optional(),
|
|
70
83
|
}),
|
|
71
84
|
)
|
|
@@ -73,59 +86,70 @@ const DingTalkAccountConfigShape = {
|
|
|
73
86
|
|
|
74
87
|
/** Connection robustness configuration */
|
|
75
88
|
|
|
76
|
-
/** Maximum
|
|
89
|
+
/** Maximum connection attempts in a single reconnect cycle before backing off or giving up. */
|
|
77
90
|
maxConnectionAttempts: z.number().int().min(1).optional().default(10),
|
|
78
91
|
|
|
79
|
-
/** Initial
|
|
92
|
+
/** Initial reconnect backoff delay in milliseconds. */
|
|
80
93
|
initialReconnectDelay: z.number().int().min(100).optional().default(1000),
|
|
81
94
|
|
|
82
|
-
/**
|
|
95
|
+
/** Upper bound for reconnect backoff delay in milliseconds. */
|
|
83
96
|
maxReconnectDelay: z.number().int().min(1000).optional().default(60000),
|
|
84
97
|
|
|
85
|
-
/**
|
|
98
|
+
/** Randomization factor added to reconnect backoff to avoid synchronized reconnect storms. */
|
|
86
99
|
reconnectJitter: z.number().min(0).max(1).optional().default(0.3),
|
|
87
100
|
|
|
88
|
-
/** Maximum
|
|
101
|
+
/** Maximum reconnect cycles before the channel stops retrying and waits for the next lifecycle restart. */
|
|
89
102
|
maxReconnectCycles: z.number().int().min(1).optional().default(10),
|
|
90
103
|
|
|
91
|
-
/**
|
|
104
|
+
/** Time limit in milliseconds for one reconnect cycle before starting a fresh cycle. */
|
|
92
105
|
reconnectDeadlineMs: z.number().int().min(5000).optional().default(50000),
|
|
93
106
|
|
|
94
|
-
/**
|
|
107
|
+
/** Enable the plugin connection manager. Disable only when you intentionally rely on DWClient native keepAlive plus autoReconnect behavior. */
|
|
95
108
|
useConnectionManager: z.boolean().optional().default(true),
|
|
96
109
|
|
|
97
|
-
/** Maximum inbound media
|
|
110
|
+
/** Maximum inbound media size in MB accepted by the plugin. When omitted, the runtime default is used. */
|
|
98
111
|
mediaMaxMb: z.number().int().min(1).optional(),
|
|
99
112
|
|
|
100
|
-
/**
|
|
113
|
+
/** Enable the underlying Stream client heartbeat. When omitted, runtime derives a default from `useConnectionManager`. */
|
|
101
114
|
keepAlive: z.boolean().optional(),
|
|
102
|
-
/** Bypass
|
|
115
|
+
/** Bypass global or system HTTP(S) proxy settings for DingTalk send, upload, and card APIs. */
|
|
103
116
|
bypassProxyForSend: z.boolean().optional().default(false),
|
|
117
|
+
/** Controls the proactive-send permission reminder shown when a conversation has not granted send rights yet. */
|
|
104
118
|
proactivePermissionHint: z
|
|
105
119
|
.object({
|
|
120
|
+
/** Show the proactive-send permission hint when the runtime detects missing DingTalk proactive permission. */
|
|
106
121
|
enabled: z.boolean().optional().default(true),
|
|
122
|
+
/** Minimum cooldown in hours before the same proactive permission hint can be shown again. */
|
|
107
123
|
cooldownHours: z.number().int().min(1).max(24 * 30).optional().default(24),
|
|
108
124
|
})
|
|
109
125
|
.optional()
|
|
110
126
|
.default({ enabled: true, cooldownHours: 24 }),
|
|
111
127
|
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
/** Deprecated compatibility flag. When true and `cardStreamingMode` is unset, runtime resolves to `cardStreamingMode: "all"`. Do not use in new configs. */
|
|
129
|
+
cardRealTimeStream: z.boolean().optional(),
|
|
130
|
+
|
|
131
|
+
/** Card streaming mode:
|
|
132
|
+
* - off: disable incremental streaming
|
|
133
|
+
* - answer: stream answer text
|
|
134
|
+
* - all: stream answer + reasoning or thinking text */
|
|
135
|
+
cardStreamingMode: CardStreamingModeSchema.optional(),
|
|
136
|
+
|
|
137
|
+
/** Throttle interval in milliseconds between AI card streaming updates. */
|
|
138
|
+
cardStreamInterval: z.number().int().min(200).optional().default(1000),
|
|
115
139
|
|
|
116
|
-
/**
|
|
140
|
+
/** Cooldown window in milliseconds after AI card trigger errors. Replies fall back to non-card delivery during this period. */
|
|
117
141
|
aicardDegradeMs: z.number().int().min(60_000).optional().default(30 * 60 * 1000),
|
|
118
142
|
|
|
119
|
-
/** Enable local learning loop
|
|
143
|
+
/** Enable the local feedback-learning loop for notes, reflections, and command-assisted learning. */
|
|
120
144
|
learningEnabled: z.boolean().optional(),
|
|
121
145
|
|
|
122
|
-
/**
|
|
146
|
+
/** Automatically apply generated learning output into session notes or global rules when available. */
|
|
123
147
|
learningAutoApply: z.boolean().optional(),
|
|
124
148
|
|
|
125
|
-
/**
|
|
149
|
+
/** Retention window in milliseconds for temporary learning notes. */
|
|
126
150
|
learningNoteTtlMs: z.number().int().min(60_000).optional(),
|
|
127
151
|
|
|
128
|
-
/**
|
|
152
|
+
/** Convert markdown tables to plain text before sending when you want more consistent DingTalk rendering. */
|
|
129
153
|
convertMarkdownTables: z.boolean().optional().default(true),
|
|
130
154
|
|
|
131
155
|
/** @mention the sender after card finalization in group chats.
|
package/src/config.ts
CHANGED
|
@@ -26,12 +26,34 @@ function normalizeLearningConfig(
|
|
|
26
26
|
learningNoteTtlMs: options.applyDefaults
|
|
27
27
|
? config.learningNoteTtlMs ?? DEFAULT_LEARNING_NOTE_TTL_MS
|
|
28
28
|
: config.learningNoteTtlMs,
|
|
29
|
+
cardStreamingMode: options.applyDefaults
|
|
30
|
+
? (config.cardStreamingMode ?? (config.cardRealTimeStream === true ? "all" : "off"))
|
|
31
|
+
: config.cardStreamingMode,
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
function stripRemovedLegacyFields(config: DingTalkConfig): DingTalkConfig {
|
|
33
|
-
const {
|
|
34
|
-
|
|
36
|
+
const {
|
|
37
|
+
verboseRealtimeStream: _verboseRealtimeStream,
|
|
38
|
+
cardStreamReasoning: _cardStreamReasoning,
|
|
39
|
+
accounts,
|
|
40
|
+
...rest
|
|
41
|
+
} = config as DingTalkConfig & {
|
|
42
|
+
verboseRealtimeStream?: unknown;
|
|
43
|
+
cardStreamReasoning?: unknown;
|
|
44
|
+
accounts?: Record<string, DingTalkConfig | undefined>;
|
|
45
|
+
};
|
|
46
|
+
const sanitizedAccounts = accounts
|
|
47
|
+
? Object.fromEntries(
|
|
48
|
+
Object.entries(accounts).map(([accountId, accountConfig]) => [
|
|
49
|
+
accountId,
|
|
50
|
+
accountConfig ? stripRemovedLegacyFields(accountConfig) : accountConfig,
|
|
51
|
+
]),
|
|
52
|
+
)
|
|
53
|
+
: undefined;
|
|
54
|
+
if (sanitizedAccounts) {
|
|
55
|
+
return { ...rest, accounts: sanitizedAccounts } as DingTalkConfig;
|
|
56
|
+
}
|
|
35
57
|
return rest as DingTalkConfig;
|
|
36
58
|
}
|
|
37
59
|
|
|
@@ -84,7 +106,7 @@ export function getConfig(cfg: OpenClawConfig, accountId?: string): DingTalkConf
|
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
if (dingtalkCfg.accounts && Object.keys(dingtalkCfg.accounts).length > 0) {
|
|
87
|
-
return dingtalkCfg;
|
|
109
|
+
return stripRemovedLegacyFields(dingtalkCfg);
|
|
88
110
|
}
|
|
89
111
|
|
|
90
112
|
return stripRemovedLegacyFields(normalizeLearningConfig(dingtalkCfg, { applyDefaults: true }));
|