@soimy/dingtalk 3.5.3 → 3.6.1
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 +4 -1
- package/index.ts +7 -0
- package/openclaw.plugin.json +153 -5
- package/package.json +1 -1
- package/src/auth.ts +5 -2
- 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-template.ts +14 -3
- package/src/card/statusline-renderer.ts +94 -0
- package/src/card-draft-controller.ts +245 -52
- package/src/card-service.ts +408 -23
- package/src/channel.ts +24 -1083
- package/src/config-schema.ts +21 -1
- package/src/config.ts +139 -66
- package/src/device-registration.ts +245 -0
- package/src/gateway/channel-gateway.ts +637 -0
- package/src/inbound-handler.ts +1276 -975
- package/src/media-utils.ts +6 -0
- package/src/message-context-store.ts +183 -85
- package/src/message-utils.ts +124 -16
- package/src/messaging/btw-deliver.ts +85 -0
- package/src/messaging/channel-actions.ts +174 -0
- package/src/messaging/channel-outbound.ts +158 -0
- package/src/onboarding.ts +333 -235
- package/src/path-utils.ts +49 -0
- package/src/platform/channel-status.ts +81 -0
- package/src/reply-strategy-card.ts +373 -64
- package/src/reply-strategy-markdown.ts +1 -1
- package/src/reply-strategy-types.ts +93 -0
- package/src/reply-strategy-with-reaction.ts +1 -1
- package/src/reply-strategy.ts +14 -72
- package/src/run-usage-store.ts +59 -0
- package/src/secret-input.ts +216 -0
- package/src/send-service.ts +115 -3
- package/src/session-state.ts +62 -0
- package/src/targeting/agent-name-matcher.ts +28 -0
- package/src/targeting/agent-routing.ts +30 -5
- package/src/types.ts +48 -157
|
@@ -146,3 +146,31 @@ export function resolveAtAgents(
|
|
|
146
146
|
hasInvalidAgentNames,
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get agent display name for statusLine in card template.
|
|
152
|
+
*
|
|
153
|
+
* Priority:
|
|
154
|
+
* 1. subAgentOptions.matchedName - user-friendly name from @mention (e.g., "代码专家")
|
|
155
|
+
* 2. agents.list lookup - name field from agent config
|
|
156
|
+
* 3. agentId fallback - technical identifier
|
|
157
|
+
*/
|
|
158
|
+
export function getAgentDisplayName(params: {
|
|
159
|
+
subAgentOptions?: { matchedName?: string };
|
|
160
|
+
agentId: string;
|
|
161
|
+
agentsList?: Array<{ id: string; name?: string }>;
|
|
162
|
+
}): string {
|
|
163
|
+
// Priority 1: sub-agent matchedName (user-friendly)
|
|
164
|
+
if (params.subAgentOptions?.matchedName) {
|
|
165
|
+
return params.subAgentOptions.matchedName;
|
|
166
|
+
}
|
|
167
|
+
// Priority 2: lookup from agents.list
|
|
168
|
+
if (params.agentsList) {
|
|
169
|
+
const agent = params.agentsList.find((a) => a.id === params.agentId);
|
|
170
|
+
if (agent?.name) {
|
|
171
|
+
return agent.name;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Priority 3: fallback to agentId
|
|
175
|
+
return params.agentId;
|
|
176
|
+
}
|
|
@@ -158,11 +158,36 @@ export async function dispatchSubAgents(params: {
|
|
|
158
158
|
const { matchedAgents, cfg, accountId, data, dingtalkConfig, sessionWebhook, extractedContent, handleMessage, downloadMedia: download, log } = params;
|
|
159
159
|
|
|
160
160
|
// Pre-download media once to avoid duplication across sub-agents
|
|
161
|
-
let preDownloadedMedia: {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
let preDownloadedMedia: {
|
|
162
|
+
mediaPath?: string;
|
|
163
|
+
mediaType?: string;
|
|
164
|
+
mediaPaths?: string[];
|
|
165
|
+
mediaTypes?: string[];
|
|
166
|
+
} | undefined;
|
|
167
|
+
const robotCode = resolveRobotCode(dingtalkConfig);
|
|
168
|
+
if (robotCode) {
|
|
169
|
+
const downloadCodes =
|
|
170
|
+
extractedContent.mediaPaths && extractedContent.mediaPaths.length > 0
|
|
171
|
+
? extractedContent.mediaPaths
|
|
172
|
+
: extractedContent.mediaPath
|
|
173
|
+
? [extractedContent.mediaPath]
|
|
174
|
+
: [];
|
|
175
|
+
const mediaPaths: string[] = [];
|
|
176
|
+
const mediaTypes: string[] = [];
|
|
177
|
+
for (const downloadCode of downloadCodes) {
|
|
178
|
+
const media = await download(dingtalkConfig, downloadCode, log);
|
|
179
|
+
if (media) {
|
|
180
|
+
mediaPaths.push(media.path);
|
|
181
|
+
mediaTypes.push(media.mimeType);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (mediaPaths.length > 0) {
|
|
185
|
+
preDownloadedMedia = {
|
|
186
|
+
mediaPath: mediaPaths[0],
|
|
187
|
+
mediaType: mediaTypes[0],
|
|
188
|
+
mediaPaths,
|
|
189
|
+
mediaTypes,
|
|
190
|
+
};
|
|
166
191
|
}
|
|
167
192
|
}
|
|
168
193
|
let helperMissingWarningSent = false;
|
package/src/types.ts
CHANGED
|
@@ -9,17 +9,14 @@
|
|
|
9
9
|
* - Session and token management
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
13
|
-
ChannelPlugin as SDKChannelPlugin,
|
|
14
|
-
OpenClawConfig,
|
|
15
|
-
} from "openclaw/plugin-sdk/core";
|
|
16
12
|
import type {
|
|
17
13
|
ChannelAccountSnapshot as SDKChannelAccountSnapshot,
|
|
18
14
|
ChannelGatewayContext as SDKChannelGatewayContext,
|
|
19
15
|
ChannelLogSink as SDKChannelLogSink,
|
|
20
16
|
} from "openclaw/plugin-sdk/channel-runtime";
|
|
17
|
+
import type { ChannelPlugin as SDKChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
21
18
|
import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
|
|
22
|
-
import {
|
|
19
|
+
import type { SecretInput } from "./secret-input";
|
|
23
20
|
|
|
24
21
|
export type AckReactionMode = "off" | "emoji" | "kaomoji";
|
|
25
22
|
// Accept arbitrary strings for backward compatibility; the recommended
|
|
@@ -33,7 +30,7 @@ export type ContextVisibilityMode = "all" | "allowlist" | "allowlist_quote";
|
|
|
33
30
|
*/
|
|
34
31
|
export interface DingTalkConfig extends OpenClawConfig {
|
|
35
32
|
clientId: string;
|
|
36
|
-
clientSecret:
|
|
33
|
+
clientSecret: SecretInput;
|
|
37
34
|
name?: string;
|
|
38
35
|
enabled?: boolean;
|
|
39
36
|
dmPolicy?: "open" | "pairing" | "allowlist";
|
|
@@ -51,7 +48,10 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
51
48
|
cardTemplateId?: string;
|
|
52
49
|
/** @deprecated 已固定使用内置模板契约 */
|
|
53
50
|
cardTemplateKey?: string;
|
|
54
|
-
groups?: Record<
|
|
51
|
+
groups?: Record<
|
|
52
|
+
string,
|
|
53
|
+
{ systemPrompt?: string; requireMention?: boolean; groupAllowFrom?: string[] }
|
|
54
|
+
>;
|
|
55
55
|
accounts?: Record<string, DingTalkConfig>;
|
|
56
56
|
// Connection robustness configuration
|
|
57
57
|
maxConnectionAttempts?: number;
|
|
@@ -95,6 +95,15 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
95
95
|
convertMarkdownTables?: boolean;
|
|
96
96
|
/** @mention the sender after card finalization in group chats; value is the message text */
|
|
97
97
|
cardAtSender?: string;
|
|
98
|
+
/** Status line visibility toggles for the AI card footer */
|
|
99
|
+
cardStatusLine?: {
|
|
100
|
+
model?: boolean;
|
|
101
|
+
effort?: boolean;
|
|
102
|
+
agent?: boolean;
|
|
103
|
+
taskTime?: boolean;
|
|
104
|
+
tokens?: boolean;
|
|
105
|
+
dapiUsage?: boolean;
|
|
106
|
+
};
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
/**
|
|
@@ -103,7 +112,7 @@ export interface DingTalkConfig extends OpenClawConfig {
|
|
|
103
112
|
export interface DingTalkChannelConfig {
|
|
104
113
|
enabled?: boolean;
|
|
105
114
|
clientId: string;
|
|
106
|
-
clientSecret:
|
|
115
|
+
clientSecret: SecretInput;
|
|
107
116
|
name?: string;
|
|
108
117
|
dmPolicy?: "open" | "pairing" | "allowlist";
|
|
109
118
|
groupPolicy?: "open" | "allowlist" | "disabled";
|
|
@@ -120,7 +129,10 @@ export interface DingTalkChannelConfig {
|
|
|
120
129
|
cardTemplateId?: string;
|
|
121
130
|
/** @deprecated 已固定使用内置模板契约 */
|
|
122
131
|
cardTemplateKey?: string;
|
|
123
|
-
groups?: Record<
|
|
132
|
+
groups?: Record<
|
|
133
|
+
string,
|
|
134
|
+
{ systemPrompt?: string; requireMention?: boolean; groupAllowFrom?: string[] }
|
|
135
|
+
>;
|
|
124
136
|
accounts?: Record<string, DingTalkConfig>;
|
|
125
137
|
maxConnectionAttempts?: number;
|
|
126
138
|
initialReconnectDelay?: number;
|
|
@@ -298,7 +310,12 @@ export interface DingTalkInboundMessage {
|
|
|
298
310
|
sessionWebhook: string;
|
|
299
311
|
}
|
|
300
312
|
|
|
301
|
-
export type QuotedRefKey =
|
|
313
|
+
export type QuotedRefKey =
|
|
314
|
+
| "msgId"
|
|
315
|
+
| "processQueryKey"
|
|
316
|
+
| "messageId"
|
|
317
|
+
| "outTrackId"
|
|
318
|
+
| "cardInstanceId";
|
|
302
319
|
|
|
303
320
|
export type AttachmentTextSource = "text" | "html" | "pdf" | "docx";
|
|
304
321
|
|
|
@@ -460,6 +477,8 @@ export interface HandleDingTalkMessageParams {
|
|
|
460
477
|
preDownloadedMedia?: {
|
|
461
478
|
mediaPath?: string;
|
|
462
479
|
mediaType?: string;
|
|
480
|
+
mediaPaths?: string[];
|
|
481
|
+
mediaTypes?: string[];
|
|
463
482
|
};
|
|
464
483
|
}
|
|
465
484
|
|
|
@@ -636,7 +655,9 @@ export interface DingTalkOutboundHandler {
|
|
|
636
655
|
deliveryMode: "direct" | "queued" | "batch";
|
|
637
656
|
resolveTarget: (params: ResolveTargetParams) => TargetResolutionResult;
|
|
638
657
|
sendText: (params: SendTextParams) => Promise<{ ok: boolean; data?: unknown; error?: unknown }>;
|
|
639
|
-
sendMedia?: (
|
|
658
|
+
sendMedia?: (
|
|
659
|
+
params: SendMediaParams,
|
|
660
|
+
) => Promise<{ ok: boolean; data?: unknown; error?: unknown }>;
|
|
640
661
|
}
|
|
641
662
|
|
|
642
663
|
/**
|
|
@@ -670,8 +691,13 @@ export interface AICardInstance {
|
|
|
670
691
|
lastUpdated: number;
|
|
671
692
|
state: AICardState; // Current card state: PROCESSING, INPUTING, FINISHED, STOPPED, FAILED
|
|
672
693
|
config?: DingTalkConfig; // Store config reference for token refresh
|
|
673
|
-
lastStreamedContent?: string;
|
|
694
|
+
lastStreamedContent?: string; // Latest copy/content text shown to user
|
|
695
|
+
lastBlockListJson?: string; // Latest rendered CardBlock[] JSON for V2 instances API
|
|
674
696
|
outTrackId?: string;
|
|
697
|
+
/** Cumulative DingTalk API call count for this card instance. */
|
|
698
|
+
dapiUsage?: number;
|
|
699
|
+
/** True after this card has successfully opened DingTalk's streaming lifecycle. */
|
|
700
|
+
streamLifecycleOpened?: boolean;
|
|
675
701
|
}
|
|
676
702
|
|
|
677
703
|
/**
|
|
@@ -731,150 +757,15 @@ export interface ConnectionAttemptResult {
|
|
|
731
757
|
error?: Error;
|
|
732
758
|
nextDelay?: number;
|
|
733
759
|
}
|
|
734
|
-
|
|
735
|
-
// ============ Onboarding Helper Functions ============
|
|
736
|
-
|
|
737
|
-
const DEFAULT_ACCOUNT_ID = "default";
|
|
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
|
-
|
|
766
|
-
/**
|
|
767
|
-
* List all DingTalk account IDs from config
|
|
768
|
-
*/
|
|
769
|
-
export function listDingTalkAccountIds(cfg: OpenClawConfig): string[] {
|
|
770
|
-
const dingtalk = cfg.channels?.dingtalk as DingTalkChannelConfig | undefined;
|
|
771
|
-
if (!dingtalk) {
|
|
772
|
-
return [];
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
const accountIds: string[] = [];
|
|
776
|
-
|
|
777
|
-
// Check for direct configuration (default account)
|
|
778
|
-
if (dingtalk.clientId || dingtalk.clientSecret) {
|
|
779
|
-
accountIds.push(DEFAULT_ACCOUNT_ID);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// Check accounts object
|
|
783
|
-
if (dingtalk.accounts) {
|
|
784
|
-
accountIds.push(...Object.keys(dingtalk.accounts));
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
return accountIds;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
760
|
/**
|
|
791
|
-
*
|
|
761
|
+
* CardBlock types for AI Card v2 blockList.
|
|
762
|
+
* - 0 = answer (markdown text)
|
|
763
|
+
* - 1 = thinking process
|
|
764
|
+
* - 2 = tool result
|
|
765
|
+
* - 3 = image (uploaded mediaId)
|
|
792
766
|
*/
|
|
793
|
-
export
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
/**
|
|
799
|
-
* Resolve a specific DingTalk account configuration
|
|
800
|
-
*/
|
|
801
|
-
export function resolveDingTalkAccount(
|
|
802
|
-
cfg: OpenClawConfig,
|
|
803
|
-
accountId?: string | null,
|
|
804
|
-
): ResolvedDingTalkAccount {
|
|
805
|
-
const id = accountId || DEFAULT_ACCOUNT_ID;
|
|
806
|
-
const dingtalk = cfg.channels?.dingtalk as DingTalkChannelConfig | undefined;
|
|
807
|
-
|
|
808
|
-
// If default account, return top-level config
|
|
809
|
-
if (id === DEFAULT_ACCOUNT_ID) {
|
|
810
|
-
const rawConfig: DingTalkConfig = {
|
|
811
|
-
clientId: dingtalk?.clientId ?? "",
|
|
812
|
-
clientSecret: dingtalk?.clientSecret ?? "",
|
|
813
|
-
name: dingtalk?.name,
|
|
814
|
-
enabled: dingtalk?.enabled,
|
|
815
|
-
dmPolicy: dingtalk?.dmPolicy,
|
|
816
|
-
groupPolicy: dingtalk?.groupPolicy,
|
|
817
|
-
allowFrom: dingtalk?.allowFrom,
|
|
818
|
-
groupAllowFrom: dingtalk?.groupAllowFrom,
|
|
819
|
-
displayNameResolution: dingtalk?.displayNameResolution,
|
|
820
|
-
contextVisibility: dingtalk?.contextVisibility,
|
|
821
|
-
journalTTLDays: dingtalk?.journalTTLDays,
|
|
822
|
-
ackReaction: dingtalk?.ackReaction,
|
|
823
|
-
debug: dingtalk?.debug,
|
|
824
|
-
messageType: dingtalk?.messageType,
|
|
825
|
-
cardTemplateId: dingtalk?.cardTemplateId,
|
|
826
|
-
cardTemplateKey: dingtalk?.cardTemplateKey,
|
|
827
|
-
groups: dingtalk?.groups,
|
|
828
|
-
accounts: dingtalk?.accounts,
|
|
829
|
-
maxConnectionAttempts: dingtalk?.maxConnectionAttempts,
|
|
830
|
-
initialReconnectDelay: dingtalk?.initialReconnectDelay,
|
|
831
|
-
maxReconnectDelay: dingtalk?.maxReconnectDelay,
|
|
832
|
-
reconnectJitter: dingtalk?.reconnectJitter,
|
|
833
|
-
maxReconnectCycles: dingtalk?.maxReconnectCycles,
|
|
834
|
-
reconnectDeadlineMs: dingtalk?.reconnectDeadlineMs,
|
|
835
|
-
useConnectionManager: dingtalk?.useConnectionManager,
|
|
836
|
-
mediaMaxMb: dingtalk?.mediaMaxMb,
|
|
837
|
-
keepAlive: dingtalk?.keepAlive,
|
|
838
|
-
bypassProxyForSend: dingtalk?.bypassProxyForSend,
|
|
839
|
-
proactivePermissionHint: dingtalk?.proactivePermissionHint,
|
|
840
|
-
cardStreamingMode: dingtalk?.cardStreamingMode,
|
|
841
|
-
cardRealTimeStream: dingtalk?.cardRealTimeStream,
|
|
842
|
-
cardStreamInterval: dingtalk?.cardStreamInterval,
|
|
843
|
-
aicardDegradeMs: dingtalk?.aicardDegradeMs,
|
|
844
|
-
learningEnabled: dingtalk?.learningEnabled,
|
|
845
|
-
learningAutoApply: dingtalk?.learningAutoApply,
|
|
846
|
-
learningNoteTtlMs: dingtalk?.learningNoteTtlMs,
|
|
847
|
-
convertMarkdownTables: dingtalk?.convertMarkdownTables,
|
|
848
|
-
cardAtSender: dingtalk?.cardAtSender,
|
|
849
|
-
};
|
|
850
|
-
const config = stripRemovedLegacyFieldsFromPublicAccount(rawConfig);
|
|
851
|
-
return {
|
|
852
|
-
...config,
|
|
853
|
-
accountId: id,
|
|
854
|
-
configured: Boolean(config.clientId && config.clientSecret),
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// If named account, merge channel-level defaults with account-level overrides
|
|
859
|
-
const accountConfig = dingtalk?.accounts?.[id];
|
|
860
|
-
if (accountConfig) {
|
|
861
|
-
const merged = mergeAccountWithDefaults(
|
|
862
|
-
dingtalk as DingTalkConfig,
|
|
863
|
-
accountConfig,
|
|
864
|
-
);
|
|
865
|
-
const publicMerged = stripRemovedLegacyFieldsFromPublicAccount(merged);
|
|
866
|
-
return {
|
|
867
|
-
...publicMerged,
|
|
868
|
-
accountId: id,
|
|
869
|
-
configured: Boolean(merged.clientId && merged.clientSecret),
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// Account doesn't exist, return empty config
|
|
874
|
-
return {
|
|
875
|
-
clientId: "",
|
|
876
|
-
clientSecret: "",
|
|
877
|
-
accountId: id,
|
|
878
|
-
configured: false,
|
|
879
|
-
};
|
|
880
|
-
}
|
|
767
|
+
export type CardBlock =
|
|
768
|
+
| { type: 0; markdown: string }
|
|
769
|
+
| { type: 1; markdown: string }
|
|
770
|
+
| { type: 2; markdown: string }
|
|
771
|
+
| { type: 3; mediaId: string; text?: string };
|