@soimy/dingtalk 3.3.0 → 3.4.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 +141 -12
- package/index.ts +71 -66
- package/package.json +6 -5
- package/src/access-control.ts +65 -0
- package/src/ack-reaction/dynamic-ack-reaction-controller.ts +271 -0
- package/src/ack-reaction/dynamic-ack-reaction-events.ts +123 -0
- package/src/ack-reaction/dynamic-ack-reaction-progress.ts +59 -0
- package/src/ack-reaction-classifier.ts +17 -4
- package/src/ack-reaction-service.ts +66 -19
- package/src/attachment-text-extractor.ts +2 -1
- package/src/card-service.ts +145 -257
- package/src/channel.ts +106 -47
- package/src/config-schema.ts +28 -6
- package/src/config.ts +30 -6
- package/src/connection-manager.ts +16 -5
- package/src/inbound-handler.ts +694 -520
- package/src/media-utils.ts +99 -36
- package/src/message-context-store.ts +787 -0
- package/src/message-utils.ts +221 -42
- package/src/messaging/quoted-context.ts +269 -0
- package/src/messaging/quoted-ref.ts +97 -0
- package/src/onboarding.ts +381 -269
- package/src/reply-strategy-card.ts +225 -0
- package/src/reply-strategy-markdown.ts +55 -0
- package/src/reply-strategy-with-reaction.ts +190 -0
- package/src/reply-strategy.ts +72 -0
- package/src/runtime.ts +5 -7
- package/src/send-service.ts +164 -62
- package/src/targeting/agent-name-matcher.ts +148 -0
- package/src/targeting/agent-routing.ts +181 -0
- package/src/targeting/target-directory-adapter.ts +152 -0
- package/src/targeting/target-directory-store.ts +396 -0
- package/src/targeting/target-input.ts +62 -0
- package/src/types.ts +124 -21
- package/src/quote-journal.ts +0 -242
- package/src/quoted-msg-cache.ts +0 -226
package/src/onboarding.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ChannelSetupAdapter,
|
|
3
|
+
ChannelSetupInput,
|
|
4
|
+
ChannelSetupWizard,
|
|
5
|
+
OpenClawConfig,
|
|
6
|
+
WizardPrompter,
|
|
7
|
+
} from "openclaw/plugin-sdk/setup";
|
|
8
|
+
import { DEFAULT_ACCOUNT_ID, formatDocsLink, normalizeAccountId } from "openclaw/plugin-sdk/setup";
|
|
9
|
+
import { DEFAULT_MESSAGE_CONTEXT_TTL_DAYS } from "./message-context-store.js";
|
|
3
10
|
import type { DingTalkConfig, DingTalkChannelConfig } from "./types.js";
|
|
4
11
|
import { listDingTalkAccountIds, resolveDingTalkAccount } from "./types.js";
|
|
5
|
-
import { DEFAULT_JOURNAL_TTL_DAYS } from "./quote-journal.js";
|
|
6
12
|
|
|
7
13
|
const channel = "dingtalk" as const;
|
|
8
14
|
|
|
@@ -53,8 +59,19 @@ async function promptDingTalkAccountId(options: {
|
|
|
53
59
|
message: `Use existing ${options.label} account?`,
|
|
54
60
|
initialValue: true,
|
|
55
61
|
});
|
|
56
|
-
if (useExisting
|
|
57
|
-
|
|
62
|
+
if (useExisting) {
|
|
63
|
+
if (existingIds.includes(options.currentId)) {
|
|
64
|
+
return options.currentId;
|
|
65
|
+
}
|
|
66
|
+
const selected = await options.prompter.select({
|
|
67
|
+
message: `Select existing ${options.label} account`,
|
|
68
|
+
options: existingIds.map((accountId) => ({
|
|
69
|
+
label: accountId,
|
|
70
|
+
value: accountId,
|
|
71
|
+
})),
|
|
72
|
+
initialValue: existingIds[0],
|
|
73
|
+
});
|
|
74
|
+
return normalizeAccountId(String(selected));
|
|
58
75
|
}
|
|
59
76
|
const newId = await options.prompter.text({
|
|
60
77
|
message: `New ${options.label} account ID`,
|
|
@@ -73,7 +90,7 @@ async function noteDingTalkHelp(prompter: WizardPrompter): Promise<void> {
|
|
|
73
90
|
"3. Enable 'Robot' capability",
|
|
74
91
|
"4. Configure message receiving mode as 'Stream mode'",
|
|
75
92
|
"5. Copy Client ID (AppKey) and Client Secret (AppSecret)",
|
|
76
|
-
`Docs: ${formatDocsLink("/
|
|
93
|
+
`Docs: ${formatDocsLink("https://github.com/soimy/openclaw-channel-dingtalk", "plugin docs")}`,
|
|
77
94
|
].join("\n"),
|
|
78
95
|
"DingTalk setup",
|
|
79
96
|
);
|
|
@@ -104,6 +121,13 @@ function applyAccountConfig(params: {
|
|
|
104
121
|
...(input.dmPolicy ? { dmPolicy: input.dmPolicy } : {}),
|
|
105
122
|
...(input.groupPolicy ? { groupPolicy: input.groupPolicy } : {}),
|
|
106
123
|
...(input.allowFrom && input.allowFrom.length > 0 ? { allowFrom: input.allowFrom } : {}),
|
|
124
|
+
...(input.groupAllowFrom && input.groupAllowFrom.length > 0
|
|
125
|
+
? { groupAllowFrom: input.groupAllowFrom }
|
|
126
|
+
: {}),
|
|
127
|
+
...(input.displayNameResolution ? { displayNameResolution: input.displayNameResolution } : {}),
|
|
128
|
+
...(input.mediaUrlAllowlist && input.mediaUrlAllowlist.length > 0
|
|
129
|
+
? { mediaUrlAllowlist: input.mediaUrlAllowlist }
|
|
130
|
+
: {}),
|
|
107
131
|
...(input.messageType ? { messageType: input.messageType } : {}),
|
|
108
132
|
...(input.cardTemplateId ? { cardTemplateId: input.cardTemplateId } : {}),
|
|
109
133
|
...(input.cardTemplateKey ? { cardTemplateKey: input.cardTemplateKey } : {}),
|
|
@@ -155,288 +179,376 @@ function applyAccountConfig(params: {
|
|
|
155
179
|
};
|
|
156
180
|
}
|
|
157
181
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
182
|
+
function applyGenericSetupInput(params: {
|
|
183
|
+
cfg: OpenClawConfig;
|
|
184
|
+
accountId: string;
|
|
185
|
+
input: ChannelSetupInput;
|
|
186
|
+
}): OpenClawConfig {
|
|
187
|
+
return applyAccountConfig({
|
|
188
|
+
cfg: params.cfg,
|
|
189
|
+
accountId: params.accountId,
|
|
190
|
+
input: {
|
|
191
|
+
name: params.input.name,
|
|
192
|
+
clientId: typeof params.input.token === "string" ? params.input.token.trim() : undefined,
|
|
193
|
+
clientSecret:
|
|
194
|
+
typeof params.input.password === "string" ? params.input.password.trim() : undefined,
|
|
195
|
+
robotCode: typeof params.input.code === "string" ? params.input.code.trim() : undefined,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
166
199
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (shouldPromptAccountIds && !override) {
|
|
180
|
-
accountId = await promptDingTalkAccountId({
|
|
181
|
-
cfg,
|
|
182
|
-
prompter,
|
|
183
|
-
label: "DingTalk",
|
|
184
|
-
currentId: accountId,
|
|
185
|
-
listAccountIds: listDingTalkAccountIds,
|
|
186
|
-
defaultAccountId: DEFAULT_ACCOUNT_ID,
|
|
187
|
-
});
|
|
188
|
-
}
|
|
200
|
+
function validatePositiveInteger(value: string): string | undefined {
|
|
201
|
+
const raw = String(value ?? "").trim();
|
|
202
|
+
const num = Number(raw);
|
|
203
|
+
if (!raw) {
|
|
204
|
+
return "Required";
|
|
205
|
+
}
|
|
206
|
+
if (!Number.isInteger(num) || num < 1) {
|
|
207
|
+
return "Must be an integer >= 1";
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
189
211
|
|
|
190
|
-
|
|
191
|
-
|
|
212
|
+
async function configureDingTalkAccount(params: {
|
|
213
|
+
cfg: OpenClawConfig;
|
|
214
|
+
accountId: string;
|
|
215
|
+
prompter: WizardPrompter;
|
|
216
|
+
}): Promise<OpenClawConfig> {
|
|
217
|
+
const { cfg, accountId, prompter } = params;
|
|
218
|
+
const resolved = resolveDingTalkAccount(cfg, accountId);
|
|
192
219
|
|
|
193
|
-
|
|
194
|
-
message: "Client ID (AppKey)",
|
|
195
|
-
placeholder: "dingxxxxxxxx",
|
|
196
|
-
initialValue: resolved.clientId ?? undefined,
|
|
197
|
-
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
198
|
-
});
|
|
220
|
+
await noteDingTalkHelp(prompter);
|
|
199
221
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
222
|
+
const clientId = await prompter.text({
|
|
223
|
+
message: "Client ID (AppKey)",
|
|
224
|
+
placeholder: "dingxxxxxxxx",
|
|
225
|
+
initialValue: resolved.clientId ?? undefined,
|
|
226
|
+
validate: (value: string) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
227
|
+
});
|
|
206
228
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
const clientSecret = await prompter.text({
|
|
230
|
+
message: "Client Secret (AppSecret)",
|
|
231
|
+
placeholder: "xxx-xxx-xxx-xxx",
|
|
232
|
+
initialValue: resolved.clientSecret ?? undefined,
|
|
233
|
+
validate: (value: string) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
234
|
+
});
|
|
211
235
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (wantsFullConfig) {
|
|
217
|
-
robotCode =
|
|
218
|
-
String(
|
|
219
|
-
await prompter.text({
|
|
220
|
-
message: "Robot Code",
|
|
221
|
-
placeholder: "dingxxxxxxxx",
|
|
222
|
-
initialValue: resolved.robotCode ?? undefined,
|
|
223
|
-
}),
|
|
224
|
-
).trim() || undefined;
|
|
225
|
-
|
|
226
|
-
corpId =
|
|
227
|
-
String(
|
|
228
|
-
await prompter.text({
|
|
229
|
-
message: "Corp ID",
|
|
230
|
-
placeholder: "dingxxxxxxxx",
|
|
231
|
-
initialValue: resolved.corpId ?? undefined,
|
|
232
|
-
}),
|
|
233
|
-
).trim() || undefined;
|
|
234
|
-
|
|
235
|
-
agentId =
|
|
236
|
-
String(
|
|
237
|
-
await prompter.text({
|
|
238
|
-
message: "Agent ID",
|
|
239
|
-
placeholder: "123456789",
|
|
240
|
-
initialValue: resolved.agentId ? String(resolved.agentId) : undefined,
|
|
241
|
-
}),
|
|
242
|
-
).trim() || undefined;
|
|
243
|
-
}
|
|
236
|
+
const wantsFullConfig = await prompter.confirm({
|
|
237
|
+
message: "Configure robot code, corp ID, and agent ID? (recommended for full features)",
|
|
238
|
+
initialValue: false,
|
|
239
|
+
});
|
|
244
240
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
241
|
+
let robotCode: string | undefined;
|
|
242
|
+
let corpId: string | undefined;
|
|
243
|
+
let agentId: string | undefined;
|
|
244
|
+
|
|
245
|
+
if (wantsFullConfig) {
|
|
246
|
+
robotCode =
|
|
247
|
+
String(
|
|
248
|
+
await prompter.text({
|
|
249
|
+
message: "Robot Code",
|
|
250
|
+
placeholder: "dingxxxxxxxx",
|
|
251
|
+
initialValue: resolved.robotCode ?? undefined,
|
|
252
|
+
}),
|
|
253
|
+
).trim() || undefined;
|
|
254
|
+
|
|
255
|
+
corpId =
|
|
256
|
+
String(
|
|
257
|
+
await prompter.text({
|
|
258
|
+
message: "Corp ID",
|
|
259
|
+
placeholder: "dingxxxxxxxx",
|
|
260
|
+
initialValue: resolved.corpId ?? undefined,
|
|
261
|
+
}),
|
|
262
|
+
).trim() || undefined;
|
|
263
|
+
|
|
264
|
+
agentId =
|
|
265
|
+
String(
|
|
266
|
+
await prompter.text({
|
|
267
|
+
message: "Agent ID",
|
|
268
|
+
placeholder: "123456789",
|
|
269
|
+
initialValue: resolved.agentId ? String(resolved.agentId) : undefined,
|
|
270
|
+
}),
|
|
271
|
+
).trim() || undefined;
|
|
272
|
+
}
|
|
249
273
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (wantsCardMode) {
|
|
255
|
-
await prompter.note(
|
|
256
|
-
[
|
|
257
|
-
"Create an AI card template in DingTalk Developer Console:",
|
|
258
|
-
"https://open-dev.dingtalk.com/fe/card",
|
|
259
|
-
"1. Go to 'My Templates' > 'Create Template'",
|
|
260
|
-
"2. Select 'AI Card' scenario",
|
|
261
|
-
"3. Design your card and publish",
|
|
262
|
-
"4. Copy the Template ID (e.g., xxx.schema)",
|
|
263
|
-
].join("\n"),
|
|
264
|
-
"Card Template Setup",
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
cardTemplateId =
|
|
268
|
-
String(
|
|
269
|
-
await prompter.text({
|
|
270
|
-
message: "Card Template ID",
|
|
271
|
-
placeholder: "xxxxx-xxxxx-xxxxx.schema",
|
|
272
|
-
initialValue: resolved.cardTemplateId ?? undefined,
|
|
273
|
-
}),
|
|
274
|
-
).trim() || undefined;
|
|
275
|
-
|
|
276
|
-
cardTemplateKey =
|
|
277
|
-
String(
|
|
278
|
-
await prompter.text({
|
|
279
|
-
message: "Card Template Key (content field name)",
|
|
280
|
-
placeholder: "content",
|
|
281
|
-
initialValue: resolved.cardTemplateKey ?? "content",
|
|
282
|
-
}),
|
|
283
|
-
).trim() || "content";
|
|
284
|
-
|
|
285
|
-
messageType = "card";
|
|
286
|
-
}
|
|
274
|
+
const wantsCardMode = await prompter.confirm({
|
|
275
|
+
message: "Enable AI interactive card mode? (for streaming AI responses)",
|
|
276
|
+
initialValue: resolved.messageType === "card",
|
|
277
|
+
});
|
|
287
278
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
279
|
+
let cardTemplateId: string | undefined;
|
|
280
|
+
let cardTemplateKey: string | undefined;
|
|
281
|
+
let messageType: "markdown" | "card" = "markdown";
|
|
282
|
+
|
|
283
|
+
if (wantsCardMode) {
|
|
284
|
+
await prompter.note(
|
|
285
|
+
[
|
|
286
|
+
"Create an AI card template in DingTalk Developer Console:",
|
|
287
|
+
"https://open-dev.dingtalk.com/fe/card",
|
|
288
|
+
"1. Go to 'My Templates' > 'Create Template'",
|
|
289
|
+
"2. Select 'AI Card' scenario",
|
|
290
|
+
"3. Design your card and publish",
|
|
291
|
+
"4. Copy the Template ID (e.g., xxx.schema)",
|
|
292
|
+
].join("\n"),
|
|
293
|
+
"Card Template Setup",
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
cardTemplateId =
|
|
297
|
+
String(
|
|
298
|
+
await prompter.text({
|
|
299
|
+
message: "Card Template ID",
|
|
300
|
+
placeholder: "xxxxx-xxxxx-xxxxx.schema",
|
|
301
|
+
initialValue: resolved.cardTemplateId ?? undefined,
|
|
302
|
+
}),
|
|
303
|
+
).trim() || undefined;
|
|
304
|
+
|
|
305
|
+
cardTemplateKey =
|
|
306
|
+
String(
|
|
307
|
+
await prompter.text({
|
|
308
|
+
message: "Card Template Key (content field name)",
|
|
309
|
+
placeholder: "content",
|
|
310
|
+
initialValue: resolved.cardTemplateKey ?? "content",
|
|
311
|
+
}),
|
|
312
|
+
).trim() || "content";
|
|
313
|
+
|
|
314
|
+
messageType = "card";
|
|
315
|
+
}
|
|
296
316
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
317
|
+
const dmPolicyValue = await prompter.select({
|
|
318
|
+
message: "Direct message policy",
|
|
319
|
+
options: [
|
|
320
|
+
{ label: "Open - anyone can DM", value: "open" },
|
|
321
|
+
{ label: "Allowlist - only allowed users", value: "allowlist" },
|
|
322
|
+
],
|
|
323
|
+
initialValue: resolved.dmPolicy ?? "open",
|
|
324
|
+
});
|
|
306
325
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const mediaUrlAllowlistParsed = parseList(String(mediaUrlAllowlistEntry ?? ""));
|
|
313
|
-
const mediaUrlAllowlist = mediaUrlAllowlistParsed.length > 0 ? mediaUrlAllowlistParsed : undefined;
|
|
314
|
-
|
|
315
|
-
const groupPolicyValue = await prompter.select({
|
|
316
|
-
message: "Group message policy",
|
|
317
|
-
options: [
|
|
318
|
-
{ label: "Open - any group can use bot", value: "open" },
|
|
319
|
-
{ label: "Allowlist - only allowed groups", value: "allowlist" },
|
|
320
|
-
],
|
|
321
|
-
initialValue: resolved.groupPolicy ?? "open",
|
|
326
|
+
let allowFrom: string[] | undefined;
|
|
327
|
+
if (dmPolicyValue === "allowlist") {
|
|
328
|
+
const entry = await prompter.text({
|
|
329
|
+
message: "Allowed user IDs (comma-separated)",
|
|
330
|
+
placeholder: "user1, user2",
|
|
322
331
|
});
|
|
332
|
+
const parsed = parseList(String(entry ?? ""));
|
|
333
|
+
allowFrom = parsed.length > 0 ? parsed : undefined;
|
|
334
|
+
}
|
|
323
335
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
if (!Number.isInteger(num) || num < 1) {
|
|
343
|
-
return "Must be an integer >= 1";
|
|
344
|
-
}
|
|
345
|
-
return undefined;
|
|
346
|
-
},
|
|
347
|
-
}),
|
|
348
|
-
).trim(),
|
|
349
|
-
);
|
|
350
|
-
maxReconnectCycles = Number.isInteger(parsedCycles) && parsedCycles > 0 ? parsedCycles : 10;
|
|
351
|
-
}
|
|
336
|
+
const mediaUrlAllowlistEntry = await prompter.text({
|
|
337
|
+
message: "Media URL allowlist (comma-separated host/IP/CIDR, optional)",
|
|
338
|
+
placeholder: "cdn.example.com, 192.168.1.23, 10.0.0.0/8",
|
|
339
|
+
initialValue: (resolved.mediaUrlAllowlist || []).join(", ") || undefined,
|
|
340
|
+
});
|
|
341
|
+
const mediaUrlAllowlistParsed = parseList(String(mediaUrlAllowlistEntry ?? ""));
|
|
342
|
+
const mediaUrlAllowlist = mediaUrlAllowlistParsed.length > 0 ? mediaUrlAllowlistParsed : undefined;
|
|
343
|
+
|
|
344
|
+
const groupPolicyValue = await prompter.select({
|
|
345
|
+
message: "Group message policy",
|
|
346
|
+
options: [
|
|
347
|
+
{ label: "Open - any group can use bot", value: "open" },
|
|
348
|
+
{ label: "Allowlist - only allowed groups", value: "allowlist" },
|
|
349
|
+
{ label: "Disabled - block all group messages", value: "disabled" },
|
|
350
|
+
],
|
|
351
|
+
initialValue: resolved.groupPolicy ?? "open",
|
|
352
|
+
});
|
|
352
353
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
typeof resolved.mediaMaxMb === "number" ? String(resolved.mediaMaxMb) : "20",
|
|
366
|
-
validate: (value) => {
|
|
367
|
-
const raw = String(value ?? "").trim();
|
|
368
|
-
const num = Number(raw);
|
|
369
|
-
if (!raw) {
|
|
370
|
-
return "Required";
|
|
371
|
-
}
|
|
372
|
-
if (!Number.isInteger(num) || num < 1) {
|
|
373
|
-
return "Must be an integer >= 1";
|
|
374
|
-
}
|
|
375
|
-
return undefined;
|
|
376
|
-
},
|
|
377
|
-
}),
|
|
378
|
-
).trim(),
|
|
379
|
-
);
|
|
380
|
-
mediaMaxMb = Number.isInteger(parsedMediaMax) && parsedMediaMax > 0 ? parsedMediaMax : 20;
|
|
381
|
-
}
|
|
354
|
+
if (groupPolicyValue === "allowlist") {
|
|
355
|
+
await prompter.note(
|
|
356
|
+
[
|
|
357
|
+
'groupPolicy=allowlist requires "groups" config to specify allowed group IDs.',
|
|
358
|
+
"After setup, manually add group conversationIds to your config:",
|
|
359
|
+
"",
|
|
360
|
+
' "groups": { "cidXXX": {}, "cidYYY": { "systemPrompt": "..." } }',
|
|
361
|
+
"",
|
|
362
|
+
'Groups not listed will be blocked. Use "*" as key to allow all groups.',
|
|
363
|
+
].join("\n"),
|
|
364
|
+
);
|
|
365
|
+
}
|
|
382
366
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
367
|
+
let groupAllowFrom: string[] | undefined;
|
|
368
|
+
if (groupPolicyValue !== "disabled") {
|
|
369
|
+
const groupAllowFromEntry = await prompter.text({
|
|
370
|
+
message: "Group sender allowlist - user IDs allowed in groups (comma-separated, optional)",
|
|
371
|
+
placeholder: "user1, user2",
|
|
372
|
+
initialValue: (resolved.groupAllowFrom || []).join(", ") || undefined,
|
|
387
373
|
});
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
374
|
+
const parsedGroupAllowFrom = parseList(String(groupAllowFromEntry ?? ""));
|
|
375
|
+
groupAllowFrom = parsedGroupAllowFrom.length > 0 ? parsedGroupAllowFrom : undefined;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
await prompter.note(
|
|
379
|
+
[
|
|
380
|
+
"Enabling learned displayName target resolution has tradeoffs:",
|
|
381
|
+
"- learned names come from observed inbound messages and can become stale",
|
|
382
|
+
"- duplicate display names can resolve to the wrong group or user",
|
|
383
|
+
'- current upstream target resolution does not provide requester authz, so "all" applies to every caller that can reach the send flow',
|
|
384
|
+
"Use explicit IDs for sensitive or high-risk deliveries.",
|
|
385
|
+
].join("\n"),
|
|
386
|
+
"displayName resolution risk",
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const displayNameResolutionValue = await prompter.select({
|
|
390
|
+
message: "Learned displayName target resolution",
|
|
391
|
+
options: [
|
|
392
|
+
{
|
|
393
|
+
label: "Disabled - require explicit IDs",
|
|
394
|
+
value: "disabled",
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
label: "All - learned lookup for all callers (higher risk)",
|
|
398
|
+
value: "all",
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
initialValue: resolved.displayNameResolution ?? "disabled",
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
let maxReconnectCycles: number | undefined;
|
|
405
|
+
const wantsReconnectLimits = await prompter.confirm({
|
|
406
|
+
message: "Configure runtime reconnect cycle limit? (recommended)",
|
|
407
|
+
initialValue: typeof resolved.maxReconnectCycles === "number",
|
|
408
|
+
});
|
|
409
|
+
if (wantsReconnectLimits) {
|
|
410
|
+
const parsedCycles = Number(
|
|
411
|
+
String(
|
|
412
|
+
await prompter.text({
|
|
413
|
+
message: "Max runtime reconnect cycles",
|
|
414
|
+
placeholder: "10",
|
|
415
|
+
initialValue: String(resolved.maxReconnectCycles ?? 10),
|
|
416
|
+
validate: (value: string) => validatePositiveInteger(value),
|
|
417
|
+
}),
|
|
418
|
+
).trim(),
|
|
419
|
+
);
|
|
420
|
+
maxReconnectCycles = Number.isInteger(parsedCycles) && parsedCycles > 0 ? parsedCycles : 10;
|
|
421
|
+
}
|
|
417
422
|
|
|
418
|
-
|
|
423
|
+
let mediaMaxMb: number | undefined;
|
|
424
|
+
const wantsMediaMax = await prompter.confirm({
|
|
425
|
+
message: "Configure inbound media max size in MB? (optional)",
|
|
426
|
+
initialValue: typeof resolved.mediaMaxMb === "number",
|
|
427
|
+
});
|
|
428
|
+
if (wantsMediaMax) {
|
|
429
|
+
const parsedMediaMax = Number(
|
|
430
|
+
String(
|
|
431
|
+
await prompter.text({
|
|
432
|
+
message: "Max inbound media size (MB)",
|
|
433
|
+
placeholder: "20",
|
|
434
|
+
initialValue:
|
|
435
|
+
typeof resolved.mediaMaxMb === "number" ? String(resolved.mediaMaxMb) : "20",
|
|
436
|
+
validate: (value: string) => validatePositiveInteger(value),
|
|
437
|
+
}),
|
|
438
|
+
).trim(),
|
|
439
|
+
);
|
|
440
|
+
mediaMaxMb = Number.isInteger(parsedMediaMax) && parsedMediaMax > 0 ? parsedMediaMax : 20;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let journalTTLDays: number | undefined;
|
|
444
|
+
const wantsJournalTTL = await prompter.confirm({
|
|
445
|
+
message: "Configure quote journal retention in days?",
|
|
446
|
+
initialValue: typeof resolved.journalTTLDays === "number",
|
|
447
|
+
});
|
|
448
|
+
if (wantsJournalTTL) {
|
|
449
|
+
const parsedJournalTTL = Number(
|
|
450
|
+
String(
|
|
451
|
+
await prompter.text({
|
|
452
|
+
message: "Quote journal retention days",
|
|
453
|
+
placeholder: String(DEFAULT_MESSAGE_CONTEXT_TTL_DAYS),
|
|
454
|
+
initialValue:
|
|
455
|
+
typeof resolved.journalTTLDays === "number"
|
|
456
|
+
? String(resolved.journalTTLDays)
|
|
457
|
+
: String(DEFAULT_MESSAGE_CONTEXT_TTL_DAYS),
|
|
458
|
+
validate: (value: string) => validatePositiveInteger(value),
|
|
459
|
+
}),
|
|
460
|
+
).trim(),
|
|
461
|
+
);
|
|
462
|
+
journalTTLDays =
|
|
463
|
+
Number.isInteger(parsedJournalTTL) && parsedJournalTTL > 0
|
|
464
|
+
? parsedJournalTTL
|
|
465
|
+
: DEFAULT_MESSAGE_CONTEXT_TTL_DAYS;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return applyAccountConfig({
|
|
469
|
+
cfg,
|
|
470
|
+
accountId,
|
|
471
|
+
input: {
|
|
472
|
+
clientId: String(clientId).trim(),
|
|
473
|
+
clientSecret: String(clientSecret).trim(),
|
|
474
|
+
robotCode,
|
|
475
|
+
corpId,
|
|
476
|
+
agentId,
|
|
477
|
+
dmPolicy: dmPolicyValue as "open" | "allowlist",
|
|
478
|
+
groupPolicy: groupPolicyValue as "open" | "allowlist" | "disabled",
|
|
479
|
+
allowFrom,
|
|
480
|
+
groupAllowFrom,
|
|
481
|
+
displayNameResolution: displayNameResolutionValue as "disabled" | "all",
|
|
482
|
+
mediaUrlAllowlist,
|
|
483
|
+
messageType,
|
|
484
|
+
cardTemplateId,
|
|
485
|
+
cardTemplateKey,
|
|
486
|
+
maxReconnectCycles,
|
|
487
|
+
mediaMaxMb,
|
|
488
|
+
journalTTLDays,
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export const dingtalkSetupAdapter: ChannelSetupAdapter = {
|
|
494
|
+
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId ?? DEFAULT_ACCOUNT_ID),
|
|
495
|
+
applyAccountName: ({ cfg, accountId, name }) =>
|
|
496
|
+
applyAccountNameToChannelSection({ cfg, channelKey: channel, accountId, name }),
|
|
497
|
+
applyAccountConfig: ({ cfg, accountId, input }) =>
|
|
498
|
+
applyGenericSetupInput({
|
|
419
499
|
cfg,
|
|
420
500
|
accountId,
|
|
421
|
-
input
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
robotCode,
|
|
425
|
-
corpId,
|
|
426
|
-
agentId,
|
|
427
|
-
dmPolicy: dmPolicyValue as "open" | "allowlist",
|
|
428
|
-
groupPolicy: groupPolicyValue as "open" | "allowlist",
|
|
429
|
-
allowFrom,
|
|
430
|
-
mediaUrlAllowlist,
|
|
431
|
-
messageType,
|
|
432
|
-
cardTemplateId,
|
|
433
|
-
cardTemplateKey,
|
|
434
|
-
maxReconnectCycles,
|
|
435
|
-
mediaMaxMb,
|
|
436
|
-
journalTTLDays,
|
|
437
|
-
},
|
|
438
|
-
});
|
|
501
|
+
input,
|
|
502
|
+
}),
|
|
503
|
+
};
|
|
439
504
|
|
|
440
|
-
|
|
505
|
+
export const dingtalkSetupWizard: ChannelSetupWizard = {
|
|
506
|
+
channel,
|
|
507
|
+
credentials: [],
|
|
508
|
+
status: {
|
|
509
|
+
configuredLabel: "configured",
|
|
510
|
+
unconfiguredLabel: "needs setup",
|
|
511
|
+
resolveConfigured: ({ cfg }) => {
|
|
512
|
+
const accountIds = listDingTalkAccountIds(cfg);
|
|
513
|
+
return accountIds.length > 0
|
|
514
|
+
? accountIds.some((accountId) => isConfigured(resolveDingTalkAccount(cfg, accountId)))
|
|
515
|
+
: isConfigured(resolveDingTalkAccount(cfg, DEFAULT_ACCOUNT_ID));
|
|
516
|
+
},
|
|
517
|
+
resolveStatusLines: ({ configured }) => [
|
|
518
|
+
`DingTalk: ${configured ? "configured" : "needs setup"}`,
|
|
519
|
+
],
|
|
520
|
+
resolveSelectionHint: ({ configured }) =>
|
|
521
|
+
configured ? "configured" : "钉钉企业机器人",
|
|
522
|
+
resolveQuickstartScore: ({ configured }) => (configured ? 1 : 4),
|
|
523
|
+
},
|
|
524
|
+
resolveAccountIdForConfigure: async ({
|
|
525
|
+
cfg,
|
|
526
|
+
prompter,
|
|
527
|
+
accountOverride,
|
|
528
|
+
shouldPromptAccountIds,
|
|
529
|
+
listAccountIds,
|
|
530
|
+
defaultAccountId,
|
|
531
|
+
}) => {
|
|
532
|
+
const resolvedAccountId = accountOverride
|
|
533
|
+
? normalizeAccountId(accountOverride)
|
|
534
|
+
: defaultAccountId;
|
|
535
|
+
if (!shouldPromptAccountIds || accountOverride) {
|
|
536
|
+
return resolvedAccountId;
|
|
537
|
+
}
|
|
538
|
+
return await promptDingTalkAccountId({
|
|
539
|
+
cfg,
|
|
540
|
+
prompter,
|
|
541
|
+
label: "DingTalk",
|
|
542
|
+
currentId: resolvedAccountId,
|
|
543
|
+
listAccountIds,
|
|
544
|
+
defaultAccountId,
|
|
545
|
+
});
|
|
441
546
|
},
|
|
547
|
+
finalize: async ({ cfg, accountId, prompter }) => ({
|
|
548
|
+
cfg: await configureDingTalkAccount({
|
|
549
|
+
cfg,
|
|
550
|
+
accountId,
|
|
551
|
+
prompter,
|
|
552
|
+
}),
|
|
553
|
+
}),
|
|
442
554
|
};
|