@soimy/dingtalk 2.7.0 → 3.0.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 +99 -3
- package/index.ts +9 -8
- package/package.json +32 -28
- package/src/access-control.ts +55 -0
- package/src/auth.ts +47 -0
- package/src/card-service.ts +338 -0
- package/src/channel.ts +145 -1590
- package/src/config-schema.ts +7 -12
- package/src/config.ts +79 -0
- package/src/connection-manager.ts +69 -32
- package/src/dedup.ts +66 -0
- package/src/group-members-store.ts +45 -0
- package/src/inbound-handler.ts +453 -0
- package/src/logger-context.ts +17 -0
- package/src/media-utils.ts +24 -20
- package/src/message-utils.ts +156 -0
- package/src/onboarding.ts +70 -67
- package/src/peer-id-registry.ts +6 -2
- package/src/runtime.ts +2 -2
- package/src/send-service.ts +281 -0
- package/src/signature.ts +15 -0
- package/src/types.ts +45 -30
- package/src/utils.ts +29 -16
- package/clawbot.plugin.json +0 -9
- package/src/AGENTS.md +0 -63
- package/src/openclaw-channel-dingtalk.code-workspace +0 -17
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { DingTalkInboundMessage, MessageContent, SendMessageOptions } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Auto-detect markdown usage and derive message title.
|
|
5
|
+
* Title extraction follows DingTalk markdown card title constraints.
|
|
6
|
+
*/
|
|
7
|
+
export function detectMarkdownAndExtractTitle(
|
|
8
|
+
text: string,
|
|
9
|
+
options: SendMessageOptions,
|
|
10
|
+
defaultTitle: string,
|
|
11
|
+
): { useMarkdown: boolean; title: string } {
|
|
12
|
+
const hasMarkdown = /^[#*>-]|[*_`#[\]]/.test(text) || text.includes("\n");
|
|
13
|
+
const useMarkdown = options.useMarkdown !== false && (options.useMarkdown || hasMarkdown);
|
|
14
|
+
|
|
15
|
+
const title =
|
|
16
|
+
options.title ||
|
|
17
|
+
(useMarkdown
|
|
18
|
+
? text
|
|
19
|
+
.split("\n")[0]
|
|
20
|
+
.replace(/^[#*\s\->]+/, "")
|
|
21
|
+
.slice(0, 20) || defaultTitle
|
|
22
|
+
: defaultTitle);
|
|
23
|
+
|
|
24
|
+
return { useMarkdown, title };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function extractMessageContent(data: DingTalkInboundMessage): MessageContent {
|
|
28
|
+
const msgtype = data.msgtype || "text";
|
|
29
|
+
|
|
30
|
+
// Normalize quote/reply metadata into a readable text prefix so the agent can understand message context.
|
|
31
|
+
const formatQuotedContent = (): string => {
|
|
32
|
+
const textField = data.text as any;
|
|
33
|
+
|
|
34
|
+
if (textField?.isReplyMsg && textField?.repliedMsg) {
|
|
35
|
+
const repliedMsg = textField.repliedMsg;
|
|
36
|
+
const content = repliedMsg?.content;
|
|
37
|
+
|
|
38
|
+
if (content?.text) {
|
|
39
|
+
const quoteText = content.text.trim();
|
|
40
|
+
if (quoteText) {
|
|
41
|
+
return `[引用消息: "${quoteText}"]\n\n`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (content?.richText && Array.isArray(content.richText)) {
|
|
46
|
+
const textParts: string[] = [];
|
|
47
|
+
for (const part of content.richText) {
|
|
48
|
+
if (part.msgType === "text" && part.content) {
|
|
49
|
+
textParts.push(part.content);
|
|
50
|
+
} else if (part.msgType === "emoji" || part.type === "emoji") {
|
|
51
|
+
textParts.push(part.content || "[表情]");
|
|
52
|
+
} else if (part.msgType === "picture" || part.type === "picture") {
|
|
53
|
+
textParts.push("[图片]");
|
|
54
|
+
} else if (part.msgType === "at" || part.type === "at") {
|
|
55
|
+
textParts.push(`@${part.content || part.atName || "某人"}`);
|
|
56
|
+
} else if (part.text) {
|
|
57
|
+
textParts.push(part.text);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const quoteText = textParts.join("").trim();
|
|
61
|
+
if (quoteText) {
|
|
62
|
+
return `[引用消息: "${quoteText}"]\n\n`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Some clients only send originalMsgId for rich media reply messages.
|
|
68
|
+
if (textField?.isReplyMsg && !textField?.repliedMsg && data.originalMsgId) {
|
|
69
|
+
return `[这是一条引用消息,原消息ID: ${data.originalMsgId}]\n\n`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (data.quoteMessage) {
|
|
73
|
+
const quoteText = data.quoteMessage.text?.content?.trim() || "";
|
|
74
|
+
if (quoteText) {
|
|
75
|
+
return `[引用消息: "${quoteText}"]\n\n`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (data.content?.quoteContent) {
|
|
80
|
+
return `[引用消息: "${data.content.quoteContent}"]\n\n`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return "";
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const quotedPrefix = formatQuotedContent();
|
|
87
|
+
|
|
88
|
+
// Unified extraction by DingTalk msgtype for downstream routing/agent processing.
|
|
89
|
+
if (msgtype === "text") {
|
|
90
|
+
return { text: quotedPrefix + (data.text?.content?.trim() || ""), messageType: "text" };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (msgtype === "richText") {
|
|
94
|
+
const richTextParts = data.content?.richText || [];
|
|
95
|
+
let text = "";
|
|
96
|
+
let pictureDownloadCode: string | undefined;
|
|
97
|
+
// Keep first image downloadCode while preserving readable text and @mention parts.
|
|
98
|
+
for (const part of richTextParts) {
|
|
99
|
+
if (part.text && (part.type === "text" || part.type === undefined)) {
|
|
100
|
+
text += part.text;
|
|
101
|
+
}
|
|
102
|
+
if (part.type === "at" && part.atName) {
|
|
103
|
+
text += `@${part.atName} `;
|
|
104
|
+
}
|
|
105
|
+
if (part.type === "picture" && part.downloadCode && !pictureDownloadCode) {
|
|
106
|
+
pictureDownloadCode = part.downloadCode;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
text:
|
|
111
|
+
quotedPrefix + (text.trim() || (pictureDownloadCode ? "<media:image>" : "[富文本消息]")),
|
|
112
|
+
mediaPath: pictureDownloadCode,
|
|
113
|
+
mediaType: pictureDownloadCode ? "image" : undefined,
|
|
114
|
+
messageType: "richText",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (msgtype === "picture") {
|
|
119
|
+
return {
|
|
120
|
+
text: "<media:image>",
|
|
121
|
+
mediaPath: data.content?.downloadCode,
|
|
122
|
+
mediaType: "image",
|
|
123
|
+
messageType: "picture",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (msgtype === "audio") {
|
|
128
|
+
return {
|
|
129
|
+
text: data.content?.recognition || "<media:voice>",
|
|
130
|
+
mediaPath: data.content?.downloadCode,
|
|
131
|
+
mediaType: "audio",
|
|
132
|
+
messageType: "audio",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (msgtype === "video") {
|
|
137
|
+
return {
|
|
138
|
+
text: "<media:video>",
|
|
139
|
+
mediaPath: data.content?.downloadCode,
|
|
140
|
+
mediaType: "video",
|
|
141
|
+
messageType: "video",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (msgtype === "file") {
|
|
146
|
+
return {
|
|
147
|
+
text: `<media:file> (${data.content?.fileName || "文件"})`,
|
|
148
|
+
mediaPath: data.content?.downloadCode,
|
|
149
|
+
mediaType: "file",
|
|
150
|
+
messageType: "file",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Fallback: preserve unknown msgtype as readable marker.
|
|
155
|
+
return { text: data.text?.content?.trim() || `[${msgtype}消息]`, messageType: msgtype };
|
|
156
|
+
}
|
package/src/onboarding.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { OpenClawConfig, ChannelOnboardingAdapter, WizardPrompter } from
|
|
2
|
-
import { DEFAULT_ACCOUNT_ID, normalizeAccountId, formatDocsLink } from
|
|
3
|
-
import type { DingTalkConfig, DingTalkChannelConfig } from
|
|
4
|
-
import { listDingTalkAccountIds, resolveDingTalkAccount } from
|
|
1
|
+
import type { OpenClawConfig, ChannelOnboardingAdapter, WizardPrompter } from "openclaw/plugin-sdk";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId, formatDocsLink } from "openclaw/plugin-sdk";
|
|
3
|
+
import type { DingTalkConfig, DingTalkChannelConfig } from "./types.js";
|
|
4
|
+
import { listDingTalkAccountIds, resolveDingTalkAccount } from "./types.js";
|
|
5
5
|
|
|
6
|
-
const channel =
|
|
6
|
+
const channel = "dingtalk" as const;
|
|
7
7
|
|
|
8
8
|
function isConfigured(account: DingTalkConfig): boolean {
|
|
9
9
|
return Boolean(account.clientId && account.clientSecret);
|
|
@@ -23,7 +23,9 @@ function applyAccountNameToChannelSection(params: {
|
|
|
23
23
|
name?: string;
|
|
24
24
|
}): OpenClawConfig {
|
|
25
25
|
const { cfg, channelKey, name } = params;
|
|
26
|
-
if (!name)
|
|
26
|
+
if (!name) {
|
|
27
|
+
return cfg;
|
|
28
|
+
}
|
|
27
29
|
const base = cfg.channels?.[channelKey] as DingTalkChannelConfig | undefined;
|
|
28
30
|
return {
|
|
29
31
|
...cfg,
|
|
@@ -64,15 +66,15 @@ async function promptDingTalkAccountId(options: {
|
|
|
64
66
|
async function noteDingTalkHelp(prompter: WizardPrompter): Promise<void> {
|
|
65
67
|
await prompter.note(
|
|
66
68
|
[
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
"You need DingTalk application credentials.",
|
|
70
|
+
"1. Visit https://open-dev.dingtalk.com/",
|
|
71
|
+
"2. Create an enterprise internal application",
|
|
70
72
|
"3. Enable 'Robot' capability",
|
|
71
73
|
"4. Configure message receiving mode as 'Stream mode'",
|
|
72
|
-
|
|
73
|
-
`Docs: ${formatDocsLink(
|
|
74
|
-
].join(
|
|
75
|
-
|
|
74
|
+
"5. Copy Client ID (AppKey) and Client Secret (AppSecret)",
|
|
75
|
+
`Docs: ${formatDocsLink("/channels/dingtalk", "channels/dingtalk")}`,
|
|
76
|
+
].join("\n"),
|
|
77
|
+
"DingTalk setup",
|
|
76
78
|
);
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -86,7 +88,7 @@ function applyAccountConfig(params: {
|
|
|
86
88
|
|
|
87
89
|
const namedConfig = applyAccountNameToChannelSection({
|
|
88
90
|
cfg,
|
|
89
|
-
channelKey:
|
|
91
|
+
channelKey: "dingtalk",
|
|
90
92
|
accountId,
|
|
91
93
|
name: input.name,
|
|
92
94
|
});
|
|
@@ -103,7 +105,7 @@ function applyAccountConfig(params: {
|
|
|
103
105
|
...(input.allowFrom && input.allowFrom.length > 0 ? { allowFrom: input.allowFrom } : {}),
|
|
104
106
|
...(input.messageType ? { messageType: input.messageType } : {}),
|
|
105
107
|
...(input.cardTemplateId ? { cardTemplateId: input.cardTemplateId } : {}),
|
|
106
|
-
...(input.cardTemplateKey ? { cardTemplateKey: input.cardTemplateKey } : {})
|
|
108
|
+
...(input.cardTemplateKey ? { cardTemplateKey: input.cardTemplateKey } : {}),
|
|
107
109
|
};
|
|
108
110
|
|
|
109
111
|
if (useDefault) {
|
|
@@ -121,7 +123,8 @@ function applyAccountConfig(params: {
|
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
const accounts = (base as { accounts?: Record<string, unknown> }).accounts ?? {};
|
|
124
|
-
const existingAccount =
|
|
126
|
+
const existingAccount =
|
|
127
|
+
(base as { accounts?: Record<string, Record<string, unknown>> }).accounts?.[accountId] ?? {};
|
|
125
128
|
|
|
126
129
|
return {
|
|
127
130
|
...namedConfig,
|
|
@@ -155,8 +158,8 @@ export const dingtalkOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
155
158
|
return Promise.resolve({
|
|
156
159
|
channel,
|
|
157
160
|
configured,
|
|
158
|
-
statusLines: [`DingTalk: ${configured ?
|
|
159
|
-
selectionHint: configured ?
|
|
161
|
+
statusLines: [`DingTalk: ${configured ? "configured" : "needs setup"}`],
|
|
162
|
+
selectionHint: configured ? "configured" : "钉钉企业机器人",
|
|
160
163
|
quickstartScore: configured ? 1 : 4,
|
|
161
164
|
});
|
|
162
165
|
},
|
|
@@ -168,7 +171,7 @@ export const dingtalkOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
168
171
|
accountId = await promptDingTalkAccountId({
|
|
169
172
|
cfg,
|
|
170
173
|
prompter,
|
|
171
|
-
label:
|
|
174
|
+
label: "DingTalk",
|
|
172
175
|
currentId: accountId,
|
|
173
176
|
listAccountIds: listDingTalkAccountIds,
|
|
174
177
|
defaultAccountId: DEFAULT_ACCOUNT_ID,
|
|
@@ -179,21 +182,21 @@ export const dingtalkOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
179
182
|
await noteDingTalkHelp(prompter);
|
|
180
183
|
|
|
181
184
|
const clientId = await prompter.text({
|
|
182
|
-
message:
|
|
183
|
-
placeholder:
|
|
185
|
+
message: "Client ID (AppKey)",
|
|
186
|
+
placeholder: "dingxxxxxxxx",
|
|
184
187
|
initialValue: resolved.clientId ?? undefined,
|
|
185
|
-
validate: (value) => (String(value ??
|
|
188
|
+
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
186
189
|
});
|
|
187
190
|
|
|
188
191
|
const clientSecret = await prompter.text({
|
|
189
|
-
message:
|
|
190
|
-
placeholder:
|
|
192
|
+
message: "Client Secret (AppSecret)",
|
|
193
|
+
placeholder: "xxx-xxx-xxx-xxx",
|
|
191
194
|
initialValue: resolved.clientSecret ?? undefined,
|
|
192
|
-
validate: (value) => (String(value ??
|
|
195
|
+
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
193
196
|
});
|
|
194
197
|
|
|
195
198
|
const wantsFullConfig = await prompter.confirm({
|
|
196
|
-
message:
|
|
199
|
+
message: "Configure robot code, corp ID, and agent ID? (recommended for full features)",
|
|
197
200
|
initialValue: false,
|
|
198
201
|
});
|
|
199
202
|
|
|
@@ -205,100 +208,100 @@ export const dingtalkOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
205
208
|
robotCode =
|
|
206
209
|
String(
|
|
207
210
|
await prompter.text({
|
|
208
|
-
message:
|
|
209
|
-
placeholder:
|
|
211
|
+
message: "Robot Code",
|
|
212
|
+
placeholder: "dingxxxxxxxx",
|
|
210
213
|
initialValue: resolved.robotCode ?? undefined,
|
|
211
|
-
})
|
|
214
|
+
}),
|
|
212
215
|
).trim() || undefined;
|
|
213
216
|
|
|
214
217
|
corpId =
|
|
215
218
|
String(
|
|
216
219
|
await prompter.text({
|
|
217
|
-
message:
|
|
218
|
-
placeholder:
|
|
220
|
+
message: "Corp ID",
|
|
221
|
+
placeholder: "dingxxxxxxxx",
|
|
219
222
|
initialValue: resolved.corpId ?? undefined,
|
|
220
|
-
})
|
|
223
|
+
}),
|
|
221
224
|
).trim() || undefined;
|
|
222
225
|
|
|
223
226
|
agentId =
|
|
224
227
|
String(
|
|
225
228
|
await prompter.text({
|
|
226
|
-
message:
|
|
227
|
-
placeholder:
|
|
229
|
+
message: "Agent ID",
|
|
230
|
+
placeholder: "123456789",
|
|
228
231
|
initialValue: resolved.agentId ? String(resolved.agentId) : undefined,
|
|
229
|
-
})
|
|
232
|
+
}),
|
|
230
233
|
).trim() || undefined;
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
const wantsCardMode = await prompter.confirm({
|
|
234
|
-
message:
|
|
235
|
-
initialValue: resolved.messageType ===
|
|
237
|
+
message: "Enable AI interactive card mode? (for streaming AI responses)",
|
|
238
|
+
initialValue: resolved.messageType === "card",
|
|
236
239
|
});
|
|
237
240
|
|
|
238
241
|
let cardTemplateId: string | undefined;
|
|
239
242
|
let cardTemplateKey: string | undefined;
|
|
240
|
-
let messageType:
|
|
243
|
+
let messageType: "markdown" | "card" = "markdown";
|
|
241
244
|
|
|
242
245
|
if (wantsCardMode) {
|
|
243
246
|
await prompter.note(
|
|
244
247
|
[
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
"Create an AI card template in DingTalk Developer Console:",
|
|
249
|
+
"https://open-dev.dingtalk.com/fe/card",
|
|
247
250
|
"1. Go to 'My Templates' > 'Create Template'",
|
|
248
251
|
"2. Select 'AI Card' scenario",
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
].join(
|
|
252
|
-
|
|
252
|
+
"3. Design your card and publish",
|
|
253
|
+
"4. Copy the Template ID (e.g., xxx.schema)",
|
|
254
|
+
].join("\n"),
|
|
255
|
+
"Card Template Setup",
|
|
253
256
|
);
|
|
254
257
|
|
|
255
258
|
cardTemplateId =
|
|
256
259
|
String(
|
|
257
260
|
await prompter.text({
|
|
258
|
-
message:
|
|
259
|
-
placeholder:
|
|
261
|
+
message: "Card Template ID",
|
|
262
|
+
placeholder: "xxxxx-xxxxx-xxxxx.schema",
|
|
260
263
|
initialValue: resolved.cardTemplateId ?? undefined,
|
|
261
|
-
})
|
|
264
|
+
}),
|
|
262
265
|
).trim() || undefined;
|
|
263
266
|
|
|
264
267
|
cardTemplateKey =
|
|
265
268
|
String(
|
|
266
269
|
await prompter.text({
|
|
267
|
-
message:
|
|
268
|
-
placeholder:
|
|
269
|
-
initialValue: resolved.cardTemplateKey ??
|
|
270
|
-
})
|
|
271
|
-
).trim() ||
|
|
270
|
+
message: "Card Template Key (content field name)",
|
|
271
|
+
placeholder: "msgContent",
|
|
272
|
+
initialValue: resolved.cardTemplateKey ?? "msgContent",
|
|
273
|
+
}),
|
|
274
|
+
).trim() || "msgContent";
|
|
272
275
|
|
|
273
|
-
messageType =
|
|
276
|
+
messageType = "card";
|
|
274
277
|
}
|
|
275
278
|
|
|
276
279
|
const dmPolicyValue = await prompter.select({
|
|
277
|
-
message:
|
|
280
|
+
message: "Direct message policy",
|
|
278
281
|
options: [
|
|
279
|
-
{ label:
|
|
280
|
-
{ label:
|
|
282
|
+
{ label: "Open - anyone can DM", value: "open" },
|
|
283
|
+
{ label: "Allowlist - only allowed users", value: "allowlist" },
|
|
281
284
|
],
|
|
282
|
-
initialValue: resolved.dmPolicy ??
|
|
285
|
+
initialValue: resolved.dmPolicy ?? "open",
|
|
283
286
|
});
|
|
284
287
|
|
|
285
288
|
let allowFrom: string[] | undefined;
|
|
286
|
-
if (dmPolicyValue ===
|
|
289
|
+
if (dmPolicyValue === "allowlist") {
|
|
287
290
|
const entry = await prompter.text({
|
|
288
|
-
message:
|
|
289
|
-
placeholder:
|
|
291
|
+
message: "Allowed user IDs (comma-separated)",
|
|
292
|
+
placeholder: "user1, user2",
|
|
290
293
|
});
|
|
291
|
-
const parsed = parseList(String(entry ??
|
|
294
|
+
const parsed = parseList(String(entry ?? ""));
|
|
292
295
|
allowFrom = parsed.length > 0 ? parsed : undefined;
|
|
293
296
|
}
|
|
294
297
|
|
|
295
298
|
const groupPolicyValue = await prompter.select({
|
|
296
|
-
message:
|
|
299
|
+
message: "Group message policy",
|
|
297
300
|
options: [
|
|
298
|
-
{ label:
|
|
299
|
-
{ label:
|
|
301
|
+
{ label: "Open - any group can use bot", value: "open" },
|
|
302
|
+
{ label: "Allowlist - only allowed groups", value: "allowlist" },
|
|
300
303
|
],
|
|
301
|
-
initialValue: resolved.groupPolicy ??
|
|
304
|
+
initialValue: resolved.groupPolicy ?? "open",
|
|
302
305
|
});
|
|
303
306
|
|
|
304
307
|
const next = applyAccountConfig({
|
|
@@ -310,8 +313,8 @@ export const dingtalkOnboardingAdapter: ChannelOnboardingAdapter = {
|
|
|
310
313
|
robotCode,
|
|
311
314
|
corpId,
|
|
312
315
|
agentId,
|
|
313
|
-
dmPolicy: dmPolicyValue as
|
|
314
|
-
groupPolicy: groupPolicyValue as
|
|
316
|
+
dmPolicy: dmPolicyValue as "open" | "allowlist",
|
|
317
|
+
groupPolicy: groupPolicyValue as "open" | "allowlist",
|
|
315
318
|
allowFrom,
|
|
316
319
|
messageType,
|
|
317
320
|
cardTemplateId,
|
package/src/peer-id-registry.ts
CHANGED
|
@@ -14,7 +14,9 @@ const peerIdMap = new Map<string, string>();
|
|
|
14
14
|
* Register an original peer ID, keyed by its lowercased form.
|
|
15
15
|
*/
|
|
16
16
|
export function registerPeerId(originalId: string): void {
|
|
17
|
-
if (!originalId)
|
|
17
|
+
if (!originalId) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
18
20
|
peerIdMap.set(originalId.toLowerCase(), originalId);
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -23,7 +25,9 @@ export function registerPeerId(originalId: string): void {
|
|
|
23
25
|
* Returns the original if found, otherwise returns the input as-is.
|
|
24
26
|
*/
|
|
25
27
|
export function resolveOriginalPeerId(id: string): string {
|
|
26
|
-
if (!id)
|
|
28
|
+
if (!id) {
|
|
29
|
+
return id;
|
|
30
|
+
}
|
|
27
31
|
return peerIdMap.get(id.toLowerCase()) || id;
|
|
28
32
|
}
|
|
29
33
|
|
package/src/runtime.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PluginRuntime } from
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
2
|
|
|
3
3
|
let runtime: PluginRuntime | null = null;
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ export function setDingTalkRuntime(next: PluginRuntime): void {
|
|
|
8
8
|
|
|
9
9
|
export function getDingTalkRuntime(): PluginRuntime {
|
|
10
10
|
if (!runtime) {
|
|
11
|
-
throw new Error(
|
|
11
|
+
throw new Error("DingTalk runtime not initialized");
|
|
12
12
|
}
|
|
13
13
|
return runtime;
|
|
14
14
|
}
|