@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
|
@@ -14,11 +14,20 @@ import { resolveRobotCode } from "../config";
|
|
|
14
14
|
import { parseLearnCommand } from "../learning-command-service";
|
|
15
15
|
import { getDingTalkRuntime } from "../runtime";
|
|
16
16
|
import { sendBySession } from "../send-service";
|
|
17
|
+
import { getErrorMessage } from "../utils";
|
|
17
18
|
import type { AgentNameMatch, DingTalkConfig, DingTalkInboundMessage, HandleDingTalkMessageParams, Logger, MessageContent } from "../types";
|
|
18
19
|
|
|
20
|
+
export class HostRoutingHelperUnavailableError extends Error {
|
|
21
|
+
constructor(message = "DingTalk sub-agent routing requires runtime.channel.routing.buildAgentSessionKey from the host runtime.") {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "HostRoutingHelperUnavailableError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
/**
|
|
20
28
|
* Build a session key for a specific agent using the runtime API.
|
|
21
|
-
*
|
|
29
|
+
* On supported host versions, sub-agent routing must use the shared helper
|
|
30
|
+
* instead of synthesizing plugin-local fallback keys.
|
|
22
31
|
*/
|
|
23
32
|
export function buildAgentSessionKey(params: {
|
|
24
33
|
rt: ReturnType<typeof getDingTalkRuntime>;
|
|
@@ -30,31 +39,19 @@ export function buildAgentSessionKey(params: {
|
|
|
30
39
|
}): string {
|
|
31
40
|
const { rt, cfg, accountId, agentId, peerKind, peerId } = params;
|
|
32
41
|
const routing = rt.channel.routing as Record<string, unknown>;
|
|
33
|
-
if (typeof routing.buildAgentSessionKey
|
|
34
|
-
|
|
35
|
-
(routing.buildAgentSessionKey as (p: unknown) => string)({
|
|
36
|
-
agentId,
|
|
37
|
-
channel: "dingtalk",
|
|
38
|
-
accountId,
|
|
39
|
-
peer: { kind: peerKind, id: peerId },
|
|
40
|
-
dmScope: cfg.session?.dmScope,
|
|
41
|
-
identityLinks: cfg.session?.identityLinks,
|
|
42
|
-
})
|
|
43
|
-
).toLowerCase();
|
|
42
|
+
if (typeof routing.buildAgentSessionKey !== "function") {
|
|
43
|
+
throw new HostRoutingHelperUnavailableError();
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
peer: { kind: peerKind, id: peerId },
|
|
56
|
-
});
|
|
57
|
-
return `${fallbackRoute.sessionKey}:subagent:${agentId}`;
|
|
45
|
+
return (
|
|
46
|
+
(routing.buildAgentSessionKey as (p: unknown) => string)({
|
|
47
|
+
agentId,
|
|
48
|
+
channel: "dingtalk",
|
|
49
|
+
accountId,
|
|
50
|
+
peer: { kind: peerKind, id: peerId },
|
|
51
|
+
dmScope: cfg.session?.dmScope,
|
|
52
|
+
identityLinks: cfg.session?.identityLinks,
|
|
53
|
+
})
|
|
54
|
+
).toLowerCase();
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
/**
|
|
@@ -131,8 +128,8 @@ export async function resolveSubAgentRoute(params: {
|
|
|
131
128
|
await sendBySession(dingtalkConfig, sessionWebhook, `⚠️ ${fallbackReason}`, {
|
|
132
129
|
...sendOptions,
|
|
133
130
|
});
|
|
134
|
-
} catch (err:
|
|
135
|
-
log?.debug?.(`[DingTalk] Failed to send fallback notice: ${err
|
|
131
|
+
} catch (err: unknown) {
|
|
132
|
+
log?.debug?.(`[DingTalk] Failed to send fallback notice: ${getErrorMessage(err)}`);
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
135
|
|
|
@@ -168,6 +165,7 @@ export async function dispatchSubAgents(params: {
|
|
|
168
165
|
preDownloadedMedia = { mediaPath: media.path, mediaType: media.mimeType };
|
|
169
166
|
}
|
|
170
167
|
}
|
|
168
|
+
let helperMissingWarningSent = false;
|
|
171
169
|
|
|
172
170
|
for (const agentMatch of matchedAgents) {
|
|
173
171
|
try {
|
|
@@ -186,9 +184,27 @@ export async function dispatchSubAgents(params: {
|
|
|
186
184
|
preDownloadedMedia,
|
|
187
185
|
});
|
|
188
186
|
} catch (error) {
|
|
187
|
+
const message = getErrorMessage(error);
|
|
189
188
|
log?.error?.(
|
|
190
|
-
`[DingTalk] Sub-agent ${agentMatch.agentId} failed: ${
|
|
189
|
+
`[DingTalk] Sub-agent ${agentMatch.agentId} failed: ${message}`,
|
|
191
190
|
);
|
|
191
|
+
if (error instanceof HostRoutingHelperUnavailableError && !helperMissingWarningSent) {
|
|
192
|
+
helperMissingWarningSent = true;
|
|
193
|
+
try {
|
|
194
|
+
const isGroup = data.conversationType !== "1";
|
|
195
|
+
const sendOptions = isGroup ? { atUserId: data.senderId, log } : { log };
|
|
196
|
+
await sendBySession(
|
|
197
|
+
dingtalkConfig,
|
|
198
|
+
sessionWebhook,
|
|
199
|
+
"⚠️ 当前宿主版本不支持 DingTalk 子助手路由所需的 session helper,请升级 OpenClaw 后重试。",
|
|
200
|
+
sendOptions,
|
|
201
|
+
);
|
|
202
|
+
} catch (notifyError: unknown) {
|
|
203
|
+
log?.debug?.(
|
|
204
|
+
`[DingTalk] Failed to send sub-agent helper-missing notice: ${getErrorMessage(notifyError)}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
192
208
|
}
|
|
193
209
|
}
|
|
194
210
|
}
|
package/src/types.ts
CHANGED
|
@@ -25,6 +25,8 @@ export type AckReactionMode = "off" | "emoji" | "kaomoji";
|
|
|
25
25
|
// Accept arbitrary strings for backward compatibility; the recommended
|
|
26
26
|
// explicit modes remain: "off" | "emoji" | "kaomoji".
|
|
27
27
|
export type AckReactionConfigValue = string;
|
|
28
|
+
export type CardStreamingMode = "off" | "answer" | "all";
|
|
29
|
+
export type ContextVisibilityMode = "all" | "allowlist" | "allowlist_quote";
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* DingTalk channel configuration (extends base OpenClaw config)
|
|
@@ -39,6 +41,7 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
39
41
|
allowFrom?: string[];
|
|
40
42
|
groupAllowFrom?: string[];
|
|
41
43
|
displayNameResolution?: "disabled" | "all";
|
|
44
|
+
contextVisibility?: ContextVisibilityMode;
|
|
42
45
|
mediaUrlAllowlist?: string[];
|
|
43
46
|
journalTTLDays?: number;
|
|
44
47
|
ackReaction?: AckReactionConfigValue;
|
|
@@ -71,8 +74,15 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
71
74
|
enabled?: boolean;
|
|
72
75
|
cooldownHours?: number;
|
|
73
76
|
};
|
|
74
|
-
/**
|
|
77
|
+
/** Card streaming mode.
|
|
78
|
+
* - off: disable incremental streaming
|
|
79
|
+
* - answer: stream answer text
|
|
80
|
+
* - all: stream answer + reasoning text */
|
|
81
|
+
cardStreamingMode?: CardStreamingMode;
|
|
82
|
+
/** @deprecated Use `cardStreamingMode` instead. */
|
|
75
83
|
cardRealTimeStream?: boolean;
|
|
84
|
+
/** Throttle interval in ms for card stream updates (default 1000) */
|
|
85
|
+
cardStreamInterval?: number;
|
|
76
86
|
/** AICard degrade duration in milliseconds after trigger errors (default 30m) */
|
|
77
87
|
aicardDegradeMs?: number;
|
|
78
88
|
/** Enable local learning loop (events/reflections/session notes/global rules) */
|
|
@@ -100,6 +110,7 @@ export interface DingTalkChannelConfig {
|
|
|
100
110
|
allowFrom?: string[];
|
|
101
111
|
groupAllowFrom?: string[];
|
|
102
112
|
displayNameResolution?: "disabled" | "all";
|
|
113
|
+
contextVisibility?: ContextVisibilityMode;
|
|
103
114
|
mediaUrlAllowlist?: string[];
|
|
104
115
|
journalTTLDays?: number;
|
|
105
116
|
ackReaction?: AckReactionConfigValue;
|
|
@@ -131,8 +142,15 @@ export interface DingTalkChannelConfig {
|
|
|
131
142
|
enabled?: boolean;
|
|
132
143
|
cooldownHours?: number;
|
|
133
144
|
};
|
|
134
|
-
/**
|
|
145
|
+
/** Card streaming mode.
|
|
146
|
+
* - off: disable incremental streaming
|
|
147
|
+
* - answer: stream answer text
|
|
148
|
+
* - all: stream answer + reasoning text */
|
|
149
|
+
cardStreamingMode?: CardStreamingMode;
|
|
150
|
+
/** @deprecated Use `cardStreamingMode` instead. */
|
|
135
151
|
cardRealTimeStream?: boolean;
|
|
152
|
+
/** Throttle interval in ms for card stream updates (default 1000) */
|
|
153
|
+
cardStreamInterval?: number;
|
|
136
154
|
/** AICard degrade duration in milliseconds after trigger errors (default 30m) */
|
|
137
155
|
aicardDegradeMs?: number;
|
|
138
156
|
/** Enable local learning loop (events/reflections/session notes/global rules) */
|
|
@@ -234,6 +252,10 @@ export interface DingTalkInboundMessage {
|
|
|
234
252
|
atName?: string;
|
|
235
253
|
downloadCode?: string;
|
|
236
254
|
}>;
|
|
255
|
+
/** chatRecord 消息摘要 */
|
|
256
|
+
summary?: string;
|
|
257
|
+
/** chatRecord 消息标题 */
|
|
258
|
+
title?: string;
|
|
237
259
|
};
|
|
238
260
|
};
|
|
239
261
|
};
|
|
@@ -243,6 +265,8 @@ export interface DingTalkInboundMessage {
|
|
|
243
265
|
recognition?: string;
|
|
244
266
|
spaceId?: string;
|
|
245
267
|
fileId?: string;
|
|
268
|
+
text?: string;
|
|
269
|
+
title?: string;
|
|
246
270
|
biz_custom_action_url?: string;
|
|
247
271
|
richText?: Array<{
|
|
248
272
|
type: string;
|
|
@@ -712,6 +736,33 @@ export interface ConnectionAttemptResult {
|
|
|
712
736
|
|
|
713
737
|
const DEFAULT_ACCOUNT_ID = "default";
|
|
714
738
|
|
|
739
|
+
function stripRemovedLegacyFieldsFromPublicAccount(
|
|
740
|
+
config: DingTalkConfig,
|
|
741
|
+
): DingTalkConfig {
|
|
742
|
+
const {
|
|
743
|
+
cardStreamReasoning: _cardStreamReasoning,
|
|
744
|
+
verboseRealtimeStream: _verboseRealtimeStream,
|
|
745
|
+
accounts,
|
|
746
|
+
...rest
|
|
747
|
+
} = config as DingTalkConfig & {
|
|
748
|
+
cardStreamReasoning?: unknown;
|
|
749
|
+
verboseRealtimeStream?: unknown;
|
|
750
|
+
accounts?: Record<string, DingTalkConfig | undefined>;
|
|
751
|
+
};
|
|
752
|
+
const sanitizedAccounts = accounts
|
|
753
|
+
? Object.fromEntries(
|
|
754
|
+
Object.entries(accounts).map(([accountId, accountConfig]) => [
|
|
755
|
+
accountId,
|
|
756
|
+
accountConfig ? stripRemovedLegacyFieldsFromPublicAccount(accountConfig) : accountConfig,
|
|
757
|
+
]),
|
|
758
|
+
)
|
|
759
|
+
: undefined;
|
|
760
|
+
if (sanitizedAccounts) {
|
|
761
|
+
return { ...rest, accounts: sanitizedAccounts } as DingTalkConfig;
|
|
762
|
+
}
|
|
763
|
+
return rest as DingTalkConfig;
|
|
764
|
+
}
|
|
765
|
+
|
|
715
766
|
/**
|
|
716
767
|
* List all DingTalk account IDs from config
|
|
717
768
|
*/
|
|
@@ -756,7 +807,7 @@ export function resolveDingTalkAccount(
|
|
|
756
807
|
|
|
757
808
|
// If default account, return top-level config
|
|
758
809
|
if (id === DEFAULT_ACCOUNT_ID) {
|
|
759
|
-
const
|
|
810
|
+
const rawConfig: DingTalkConfig = {
|
|
760
811
|
clientId: dingtalk?.clientId ?? "",
|
|
761
812
|
clientSecret: dingtalk?.clientSecret ?? "",
|
|
762
813
|
name: dingtalk?.name,
|
|
@@ -766,6 +817,7 @@ export function resolveDingTalkAccount(
|
|
|
766
817
|
allowFrom: dingtalk?.allowFrom,
|
|
767
818
|
groupAllowFrom: dingtalk?.groupAllowFrom,
|
|
768
819
|
displayNameResolution: dingtalk?.displayNameResolution,
|
|
820
|
+
contextVisibility: dingtalk?.contextVisibility,
|
|
769
821
|
journalTTLDays: dingtalk?.journalTTLDays,
|
|
770
822
|
ackReaction: dingtalk?.ackReaction,
|
|
771
823
|
debug: dingtalk?.debug,
|
|
@@ -785,7 +837,9 @@ export function resolveDingTalkAccount(
|
|
|
785
837
|
keepAlive: dingtalk?.keepAlive,
|
|
786
838
|
bypassProxyForSend: dingtalk?.bypassProxyForSend,
|
|
787
839
|
proactivePermissionHint: dingtalk?.proactivePermissionHint,
|
|
840
|
+
cardStreamingMode: dingtalk?.cardStreamingMode,
|
|
788
841
|
cardRealTimeStream: dingtalk?.cardRealTimeStream,
|
|
842
|
+
cardStreamInterval: dingtalk?.cardStreamInterval,
|
|
789
843
|
aicardDegradeMs: dingtalk?.aicardDegradeMs,
|
|
790
844
|
learningEnabled: dingtalk?.learningEnabled,
|
|
791
845
|
learningAutoApply: dingtalk?.learningAutoApply,
|
|
@@ -793,6 +847,7 @@ export function resolveDingTalkAccount(
|
|
|
793
847
|
convertMarkdownTables: dingtalk?.convertMarkdownTables,
|
|
794
848
|
cardAtSender: dingtalk?.cardAtSender,
|
|
795
849
|
};
|
|
850
|
+
const config = stripRemovedLegacyFieldsFromPublicAccount(rawConfig);
|
|
796
851
|
return {
|
|
797
852
|
...config,
|
|
798
853
|
accountId: id,
|
|
@@ -807,8 +862,9 @@ export function resolveDingTalkAccount(
|
|
|
807
862
|
dingtalk as DingTalkConfig,
|
|
808
863
|
accountConfig,
|
|
809
864
|
);
|
|
865
|
+
const publicMerged = stripRemovedLegacyFieldsFromPublicAccount(merged);
|
|
810
866
|
return {
|
|
811
|
-
...
|
|
867
|
+
...publicMerged,
|
|
812
868
|
accountId: id,
|
|
813
869
|
configured: Boolean(merged.clientId && merged.clientSecret),
|
|
814
870
|
};
|
package/src/utils.ts
CHANGED
|
@@ -224,6 +224,31 @@ export function stringifyUnknown(value: unknown): string {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
export function parseBooleanLike(value: unknown): boolean | undefined {
|
|
228
|
+
if (typeof value === "boolean") {
|
|
229
|
+
return value;
|
|
230
|
+
}
|
|
231
|
+
if (typeof value === "number") {
|
|
232
|
+
if (value === 1) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (value === 0) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
if (typeof value === "string") {
|
|
241
|
+
const normalized = value.trim().toLowerCase();
|
|
242
|
+
if (["1", "true", "yes", "y", "on"].includes(normalized)) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
if (["0", "false", "no", "n", "off"].includes(normalized)) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
227
252
|
export function getErrorMessage(err: unknown): string {
|
|
228
253
|
if (err instanceof Error && err.message) {
|
|
229
254
|
return err.message;
|