@soimy/dingtalk 3.5.2 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -23
- package/index.ts +7 -0
- package/openclaw.plugin.json +799 -0
- package/package.json +5 -5
- package/src/card/card-markdown-image-reroute.ts +106 -0
- package/src/card/card-run-registry.ts +54 -1
- package/src/card/card-stop-handler.ts +10 -20
- package/src/card/card-streaming-mode.ts +30 -0
- package/src/card/card-template.ts +14 -3
- package/src/card/reasoning-answer-split.ts +162 -0
- package/src/card/statusline-renderer.ts +94 -0
- package/src/card-draft-controller.ts +326 -54
- package/src/card-service.ts +479 -8
- package/src/channel.ts +19 -1062
- package/src/config-schema.ts +81 -38
- package/src/config.ts +142 -4
- package/src/device-registration.ts +245 -0
- package/src/gateway/channel-gateway.ts +636 -0
- package/src/inbound-handler.ts +489 -49
- package/src/media-utils.ts +169 -7
- package/src/message-utils.ts +153 -17
- package/src/messaging/btw-deliver.ts +85 -0
- package/src/messaging/channel-actions.ts +173 -0
- package/src/messaging/channel-outbound.ts +158 -0
- package/src/messaging/quoted-file-service.ts +9 -4
- package/src/onboarding.ts +323 -205
- package/src/platform/channel-status.ts +81 -0
- package/src/plugin-sdk-channel-actions-augment.ts +11 -0
- package/src/reply-strategy-card.ts +568 -44
- package/src/reply-strategy-markdown.ts +2 -2
- package/src/reply-strategy-types.ts +93 -0
- package/src/reply-strategy-with-reaction.ts +1 -1
- package/src/reply-strategy.ts +14 -56
- package/src/run-usage-store.ts +59 -0
- package/src/send-service.ts +225 -7
- package/src/session-state.ts +62 -0
- package/src/targeting/agent-name-matcher.ts +28 -0
- package/src/targeting/agent-routing.ts +44 -28
- package/src/types.ts +49 -117
- 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
|
@@ -19,12 +19,13 @@ import type {
|
|
|
19
19
|
ChannelLogSink as SDKChannelLogSink,
|
|
20
20
|
} from "openclaw/plugin-sdk/channel-runtime";
|
|
21
21
|
import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
|
|
22
|
-
import { mergeAccountWithDefaults } from "./config";
|
|
23
22
|
|
|
24
23
|
export type AckReactionMode = "off" | "emoji" | "kaomoji";
|
|
25
24
|
// Accept arbitrary strings for backward compatibility; the recommended
|
|
26
25
|
// explicit modes remain: "off" | "emoji" | "kaomoji".
|
|
27
26
|
export type AckReactionConfigValue = string;
|
|
27
|
+
export type CardStreamingMode = "off" | "answer" | "all";
|
|
28
|
+
export type ContextVisibilityMode = "all" | "allowlist" | "allowlist_quote";
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* DingTalk channel configuration (extends base OpenClaw config)
|
|
@@ -39,6 +40,7 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
39
40
|
allowFrom?: string[];
|
|
40
41
|
groupAllowFrom?: string[];
|
|
41
42
|
displayNameResolution?: "disabled" | "all";
|
|
43
|
+
contextVisibility?: ContextVisibilityMode;
|
|
42
44
|
mediaUrlAllowlist?: string[];
|
|
43
45
|
journalTTLDays?: number;
|
|
44
46
|
ackReaction?: AckReactionConfigValue;
|
|
@@ -71,8 +73,15 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
71
73
|
enabled?: boolean;
|
|
72
74
|
cooldownHours?: number;
|
|
73
75
|
};
|
|
74
|
-
/**
|
|
76
|
+
/** Card streaming mode.
|
|
77
|
+
* - off: disable incremental streaming
|
|
78
|
+
* - answer: stream answer text
|
|
79
|
+
* - all: stream answer + reasoning text */
|
|
80
|
+
cardStreamingMode?: CardStreamingMode;
|
|
81
|
+
/** @deprecated Use `cardStreamingMode` instead. */
|
|
75
82
|
cardRealTimeStream?: boolean;
|
|
83
|
+
/** Throttle interval in ms for card stream updates (default 1000) */
|
|
84
|
+
cardStreamInterval?: number;
|
|
76
85
|
/** AICard degrade duration in milliseconds after trigger errors (default 30m) */
|
|
77
86
|
aicardDegradeMs?: number;
|
|
78
87
|
/** Enable local learning loop (events/reflections/session notes/global rules) */
|
|
@@ -85,6 +94,15 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
85
94
|
convertMarkdownTables?: boolean;
|
|
86
95
|
/** @mention the sender after card finalization in group chats; value is the message text */
|
|
87
96
|
cardAtSender?: string;
|
|
97
|
+
/** Status line visibility toggles for the AI card footer */
|
|
98
|
+
cardStatusLine?: {
|
|
99
|
+
model?: boolean;
|
|
100
|
+
effort?: boolean;
|
|
101
|
+
agent?: boolean;
|
|
102
|
+
taskTime?: boolean;
|
|
103
|
+
tokens?: boolean;
|
|
104
|
+
dapiUsage?: boolean;
|
|
105
|
+
};
|
|
88
106
|
}
|
|
89
107
|
|
|
90
108
|
/**
|
|
@@ -100,6 +118,7 @@ export interface DingTalkChannelConfig {
|
|
|
100
118
|
allowFrom?: string[];
|
|
101
119
|
groupAllowFrom?: string[];
|
|
102
120
|
displayNameResolution?: "disabled" | "all";
|
|
121
|
+
contextVisibility?: ContextVisibilityMode;
|
|
103
122
|
mediaUrlAllowlist?: string[];
|
|
104
123
|
journalTTLDays?: number;
|
|
105
124
|
ackReaction?: AckReactionConfigValue;
|
|
@@ -131,8 +150,15 @@ export interface DingTalkChannelConfig {
|
|
|
131
150
|
enabled?: boolean;
|
|
132
151
|
cooldownHours?: number;
|
|
133
152
|
};
|
|
134
|
-
/**
|
|
153
|
+
/** Card streaming mode.
|
|
154
|
+
* - off: disable incremental streaming
|
|
155
|
+
* - answer: stream answer text
|
|
156
|
+
* - all: stream answer + reasoning text */
|
|
157
|
+
cardStreamingMode?: CardStreamingMode;
|
|
158
|
+
/** @deprecated Use `cardStreamingMode` instead. */
|
|
135
159
|
cardRealTimeStream?: boolean;
|
|
160
|
+
/** Throttle interval in ms for card stream updates (default 1000) */
|
|
161
|
+
cardStreamInterval?: number;
|
|
136
162
|
/** AICard degrade duration in milliseconds after trigger errors (default 30m) */
|
|
137
163
|
aicardDegradeMs?: number;
|
|
138
164
|
/** Enable local learning loop (events/reflections/session notes/global rules) */
|
|
@@ -234,6 +260,10 @@ export interface DingTalkInboundMessage {
|
|
|
234
260
|
atName?: string;
|
|
235
261
|
downloadCode?: string;
|
|
236
262
|
}>;
|
|
263
|
+
/** chatRecord 消息摘要 */
|
|
264
|
+
summary?: string;
|
|
265
|
+
/** chatRecord 消息标题 */
|
|
266
|
+
title?: string;
|
|
237
267
|
};
|
|
238
268
|
};
|
|
239
269
|
};
|
|
@@ -243,6 +273,8 @@ export interface DingTalkInboundMessage {
|
|
|
243
273
|
recognition?: string;
|
|
244
274
|
spaceId?: string;
|
|
245
275
|
fileId?: string;
|
|
276
|
+
text?: string;
|
|
277
|
+
title?: string;
|
|
246
278
|
biz_custom_action_url?: string;
|
|
247
279
|
richText?: Array<{
|
|
248
280
|
type: string;
|
|
@@ -646,8 +678,11 @@ export interface AICardInstance {
|
|
|
646
678
|
lastUpdated: number;
|
|
647
679
|
state: AICardState; // Current card state: PROCESSING, INPUTING, FINISHED, STOPPED, FAILED
|
|
648
680
|
config?: DingTalkConfig; // Store config reference for token refresh
|
|
649
|
-
lastStreamedContent?: string;
|
|
681
|
+
lastStreamedContent?: string; // Latest copy/content text shown to user
|
|
682
|
+
lastBlockListJson?: string; // Latest rendered CardBlock[] JSON for V2 instances API
|
|
650
683
|
outTrackId?: string;
|
|
684
|
+
/** Cumulative DingTalk API call count for this card instance. */
|
|
685
|
+
dapiUsage?: number;
|
|
651
686
|
}
|
|
652
687
|
|
|
653
688
|
/**
|
|
@@ -707,118 +742,15 @@ export interface ConnectionAttemptResult {
|
|
|
707
742
|
error?: Error;
|
|
708
743
|
nextDelay?: number;
|
|
709
744
|
}
|
|
710
|
-
|
|
711
|
-
// ============ Onboarding Helper Functions ============
|
|
712
|
-
|
|
713
|
-
const DEFAULT_ACCOUNT_ID = "default";
|
|
714
|
-
|
|
715
|
-
/**
|
|
716
|
-
* List all DingTalk account IDs from config
|
|
717
|
-
*/
|
|
718
|
-
export function listDingTalkAccountIds(cfg: OpenClawConfig): string[] {
|
|
719
|
-
const dingtalk = cfg.channels?.dingtalk as DingTalkChannelConfig | undefined;
|
|
720
|
-
if (!dingtalk) {
|
|
721
|
-
return [];
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
const accountIds: string[] = [];
|
|
725
|
-
|
|
726
|
-
// Check for direct configuration (default account)
|
|
727
|
-
if (dingtalk.clientId || dingtalk.clientSecret) {
|
|
728
|
-
accountIds.push(DEFAULT_ACCOUNT_ID);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// Check accounts object
|
|
732
|
-
if (dingtalk.accounts) {
|
|
733
|
-
accountIds.push(...Object.keys(dingtalk.accounts));
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
return accountIds;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
745
|
/**
|
|
740
|
-
*
|
|
746
|
+
* CardBlock types for AI Card v2 blockList.
|
|
747
|
+
* - 0 = answer (markdown text)
|
|
748
|
+
* - 1 = thinking process
|
|
749
|
+
* - 2 = tool result
|
|
750
|
+
* - 3 = image (uploaded mediaId)
|
|
741
751
|
*/
|
|
742
|
-
export
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* Resolve a specific DingTalk account configuration
|
|
749
|
-
*/
|
|
750
|
-
export function resolveDingTalkAccount(
|
|
751
|
-
cfg: OpenClawConfig,
|
|
752
|
-
accountId?: string | null,
|
|
753
|
-
): ResolvedDingTalkAccount {
|
|
754
|
-
const id = accountId || DEFAULT_ACCOUNT_ID;
|
|
755
|
-
const dingtalk = cfg.channels?.dingtalk as DingTalkChannelConfig | undefined;
|
|
756
|
-
|
|
757
|
-
// If default account, return top-level config
|
|
758
|
-
if (id === DEFAULT_ACCOUNT_ID) {
|
|
759
|
-
const config: DingTalkConfig = {
|
|
760
|
-
clientId: dingtalk?.clientId ?? "",
|
|
761
|
-
clientSecret: dingtalk?.clientSecret ?? "",
|
|
762
|
-
name: dingtalk?.name,
|
|
763
|
-
enabled: dingtalk?.enabled,
|
|
764
|
-
dmPolicy: dingtalk?.dmPolicy,
|
|
765
|
-
groupPolicy: dingtalk?.groupPolicy,
|
|
766
|
-
allowFrom: dingtalk?.allowFrom,
|
|
767
|
-
groupAllowFrom: dingtalk?.groupAllowFrom,
|
|
768
|
-
displayNameResolution: dingtalk?.displayNameResolution,
|
|
769
|
-
journalTTLDays: dingtalk?.journalTTLDays,
|
|
770
|
-
ackReaction: dingtalk?.ackReaction,
|
|
771
|
-
debug: dingtalk?.debug,
|
|
772
|
-
messageType: dingtalk?.messageType,
|
|
773
|
-
cardTemplateId: dingtalk?.cardTemplateId,
|
|
774
|
-
cardTemplateKey: dingtalk?.cardTemplateKey,
|
|
775
|
-
groups: dingtalk?.groups,
|
|
776
|
-
accounts: dingtalk?.accounts,
|
|
777
|
-
maxConnectionAttempts: dingtalk?.maxConnectionAttempts,
|
|
778
|
-
initialReconnectDelay: dingtalk?.initialReconnectDelay,
|
|
779
|
-
maxReconnectDelay: dingtalk?.maxReconnectDelay,
|
|
780
|
-
reconnectJitter: dingtalk?.reconnectJitter,
|
|
781
|
-
maxReconnectCycles: dingtalk?.maxReconnectCycles,
|
|
782
|
-
reconnectDeadlineMs: dingtalk?.reconnectDeadlineMs,
|
|
783
|
-
useConnectionManager: dingtalk?.useConnectionManager,
|
|
784
|
-
mediaMaxMb: dingtalk?.mediaMaxMb,
|
|
785
|
-
keepAlive: dingtalk?.keepAlive,
|
|
786
|
-
bypassProxyForSend: dingtalk?.bypassProxyForSend,
|
|
787
|
-
proactivePermissionHint: dingtalk?.proactivePermissionHint,
|
|
788
|
-
cardRealTimeStream: dingtalk?.cardRealTimeStream,
|
|
789
|
-
aicardDegradeMs: dingtalk?.aicardDegradeMs,
|
|
790
|
-
learningEnabled: dingtalk?.learningEnabled,
|
|
791
|
-
learningAutoApply: dingtalk?.learningAutoApply,
|
|
792
|
-
learningNoteTtlMs: dingtalk?.learningNoteTtlMs,
|
|
793
|
-
convertMarkdownTables: dingtalk?.convertMarkdownTables,
|
|
794
|
-
cardAtSender: dingtalk?.cardAtSender,
|
|
795
|
-
};
|
|
796
|
-
return {
|
|
797
|
-
...config,
|
|
798
|
-
accountId: id,
|
|
799
|
-
configured: Boolean(config.clientId && config.clientSecret),
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// If named account, merge channel-level defaults with account-level overrides
|
|
804
|
-
const accountConfig = dingtalk?.accounts?.[id];
|
|
805
|
-
if (accountConfig) {
|
|
806
|
-
const merged = mergeAccountWithDefaults(
|
|
807
|
-
dingtalk as DingTalkConfig,
|
|
808
|
-
accountConfig,
|
|
809
|
-
);
|
|
810
|
-
return {
|
|
811
|
-
...merged,
|
|
812
|
-
accountId: id,
|
|
813
|
-
configured: Boolean(merged.clientId && merged.clientSecret),
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// Account doesn't exist, return empty config
|
|
818
|
-
return {
|
|
819
|
-
clientId: "",
|
|
820
|
-
clientSecret: "",
|
|
821
|
-
accountId: id,
|
|
822
|
-
configured: false,
|
|
823
|
-
};
|
|
824
|
-
}
|
|
752
|
+
export type CardBlock =
|
|
753
|
+
| { type: 0; markdown: string }
|
|
754
|
+
| { type: 1; markdown: string }
|
|
755
|
+
| { type: 2; markdown: string }
|
|
756
|
+
| { type: 3; mediaId: string; text?: string };
|
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;
|