@soimy/dingtalk 3.4.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/index.ts +71 -66
- package/package.json +5 -4
- package/src/channel.ts +47 -20
- package/src/config.ts +1 -1
- package/src/connection-manager.ts +16 -5
- package/src/media-utils.ts +99 -36
- package/src/onboarding.ts +372 -317
- package/src/runtime.ts +5 -7
- package/src/send-service.ts +24 -10
- package/src/targeting/agent-name-matcher.ts +1 -1
- package/src/targeting/agent-routing.ts +1 -1
- package/src/targeting/target-directory-adapter.ts +2 -1
- package/src/types.ts +11 -13
package/src/onboarding.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
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";
|
|
3
9
|
import { DEFAULT_MESSAGE_CONTEXT_TTL_DAYS } from "./message-context-store.js";
|
|
4
10
|
import type { DingTalkConfig, DingTalkChannelConfig } from "./types.js";
|
|
5
11
|
import { listDingTalkAccountIds, resolveDingTalkAccount } from "./types.js";
|
|
@@ -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,9 +121,12 @@ 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 } : {}),
|
|
107
|
-
...(input.groupAllowFrom && input.groupAllowFrom.length > 0
|
|
108
|
-
|
|
109
|
-
|
|
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 }
|
|
110
130
|
: {}),
|
|
111
131
|
...(input.messageType ? { messageType: input.messageType } : {}),
|
|
112
132
|
...(input.cardTemplateId ? { cardTemplateId: input.cardTemplateId } : {}),
|
|
@@ -159,341 +179,376 @@ function applyAccountConfig(params: {
|
|
|
159
179
|
};
|
|
160
180
|
}
|
|
161
181
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
+
}
|
|
170
199
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (shouldPromptAccountIds && !override) {
|
|
184
|
-
accountId = await promptDingTalkAccountId({
|
|
185
|
-
cfg,
|
|
186
|
-
prompter,
|
|
187
|
-
label: "DingTalk",
|
|
188
|
-
currentId: accountId,
|
|
189
|
-
listAccountIds: listDingTalkAccountIds,
|
|
190
|
-
defaultAccountId: DEFAULT_ACCOUNT_ID,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
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
|
+
}
|
|
193
211
|
|
|
194
|
-
|
|
195
|
-
|
|
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);
|
|
196
219
|
|
|
197
|
-
|
|
198
|
-
message: "Client ID (AppKey)",
|
|
199
|
-
placeholder: "dingxxxxxxxx",
|
|
200
|
-
initialValue: resolved.clientId ?? undefined,
|
|
201
|
-
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
202
|
-
});
|
|
220
|
+
await noteDingTalkHelp(prompter);
|
|
203
221
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
});
|
|
210
228
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
});
|
|
215
235
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (wantsFullConfig) {
|
|
221
|
-
robotCode =
|
|
222
|
-
String(
|
|
223
|
-
await prompter.text({
|
|
224
|
-
message: "Robot Code",
|
|
225
|
-
placeholder: "dingxxxxxxxx",
|
|
226
|
-
initialValue: resolved.robotCode ?? undefined,
|
|
227
|
-
}),
|
|
228
|
-
).trim() || undefined;
|
|
229
|
-
|
|
230
|
-
corpId =
|
|
231
|
-
String(
|
|
232
|
-
await prompter.text({
|
|
233
|
-
message: "Corp ID",
|
|
234
|
-
placeholder: "dingxxxxxxxx",
|
|
235
|
-
initialValue: resolved.corpId ?? undefined,
|
|
236
|
-
}),
|
|
237
|
-
).trim() || undefined;
|
|
238
|
-
|
|
239
|
-
agentId =
|
|
240
|
-
String(
|
|
241
|
-
await prompter.text({
|
|
242
|
-
message: "Agent ID",
|
|
243
|
-
placeholder: "123456789",
|
|
244
|
-
initialValue: resolved.agentId ? String(resolved.agentId) : undefined,
|
|
245
|
-
}),
|
|
246
|
-
).trim() || undefined;
|
|
247
|
-
}
|
|
236
|
+
const wantsFullConfig = await prompter.confirm({
|
|
237
|
+
message: "Configure robot code, corp ID, and agent ID? (recommended for full features)",
|
|
238
|
+
initialValue: false,
|
|
239
|
+
});
|
|
248
240
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
}
|
|
253
273
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (wantsCardMode) {
|
|
259
|
-
await prompter.note(
|
|
260
|
-
[
|
|
261
|
-
"Create an AI card template in DingTalk Developer Console:",
|
|
262
|
-
"https://open-dev.dingtalk.com/fe/card",
|
|
263
|
-
"1. Go to 'My Templates' > 'Create Template'",
|
|
264
|
-
"2. Select 'AI Card' scenario",
|
|
265
|
-
"3. Design your card and publish",
|
|
266
|
-
"4. Copy the Template ID (e.g., xxx.schema)",
|
|
267
|
-
].join("\n"),
|
|
268
|
-
"Card Template Setup",
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
cardTemplateId =
|
|
272
|
-
String(
|
|
273
|
-
await prompter.text({
|
|
274
|
-
message: "Card Template ID",
|
|
275
|
-
placeholder: "xxxxx-xxxxx-xxxxx.schema",
|
|
276
|
-
initialValue: resolved.cardTemplateId ?? undefined,
|
|
277
|
-
}),
|
|
278
|
-
).trim() || undefined;
|
|
279
|
-
|
|
280
|
-
cardTemplateKey =
|
|
281
|
-
String(
|
|
282
|
-
await prompter.text({
|
|
283
|
-
message: "Card Template Key (content field name)",
|
|
284
|
-
placeholder: "content",
|
|
285
|
-
initialValue: resolved.cardTemplateKey ?? "content",
|
|
286
|
-
}),
|
|
287
|
-
).trim() || "content";
|
|
288
|
-
|
|
289
|
-
messageType = "card";
|
|
290
|
-
}
|
|
274
|
+
const wantsCardMode = await prompter.confirm({
|
|
275
|
+
message: "Enable AI interactive card mode? (for streaming AI responses)",
|
|
276
|
+
initialValue: resolved.messageType === "card",
|
|
277
|
+
});
|
|
291
278
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
{ label: "Open - anyone can DM", value: "open" },
|
|
296
|
-
{ label: "Allowlist - only allowed users", value: "allowlist" },
|
|
297
|
-
],
|
|
298
|
-
initialValue: resolved.dmPolicy ?? "open",
|
|
299
|
-
});
|
|
279
|
+
let cardTemplateId: string | undefined;
|
|
280
|
+
let cardTemplateKey: string | undefined;
|
|
281
|
+
let messageType: "markdown" | "card" = "markdown";
|
|
300
282
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
+
);
|
|
310
295
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
+
}
|
|
328
316
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
"Groups not listed will be blocked. Use \"*\" as key to allow all groups.",
|
|
338
|
-
].join("\n"),
|
|
339
|
-
);
|
|
340
|
-
}
|
|
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
|
+
});
|
|
341
325
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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",
|
|
331
|
+
});
|
|
332
|
+
const parsed = parseList(String(entry ?? ""));
|
|
333
|
+
allowFrom = parsed.length > 0 ? parsed : undefined;
|
|
334
|
+
}
|
|
335
|
+
|
|
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
|
|
|
354
|
+
if (groupPolicyValue === "allowlist") {
|
|
353
355
|
await prompter.note(
|
|
354
356
|
[
|
|
355
|
-
"
|
|
356
|
-
"
|
|
357
|
-
"
|
|
358
|
-
"
|
|
359
|
-
"
|
|
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.',
|
|
360
363
|
].join("\n"),
|
|
361
|
-
"displayName resolution risk",
|
|
362
364
|
);
|
|
365
|
+
}
|
|
363
366
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
},
|
|
371
|
-
{
|
|
372
|
-
label: "All - learned lookup for all callers (higher risk)",
|
|
373
|
-
value: "all",
|
|
374
|
-
},
|
|
375
|
-
],
|
|
376
|
-
initialValue: resolved.displayNameResolution ?? "disabled",
|
|
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,
|
|
377
373
|
});
|
|
374
|
+
const parsedGroupAllowFrom = parseList(String(groupAllowFromEntry ?? ""));
|
|
375
|
+
groupAllowFrom = parsedGroupAllowFrom.length > 0 ? parsedGroupAllowFrom : undefined;
|
|
376
|
+
}
|
|
378
377
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
placeholder: "10",
|
|
390
|
-
initialValue: String(resolved.maxReconnectCycles ?? 10),
|
|
391
|
-
validate: (value) => {
|
|
392
|
-
const raw = String(value ?? "").trim();
|
|
393
|
-
const num = Number(raw);
|
|
394
|
-
if (!raw) {
|
|
395
|
-
return "Required";
|
|
396
|
-
}
|
|
397
|
-
if (!Number.isInteger(num) || num < 1) {
|
|
398
|
-
return "Must be an integer >= 1";
|
|
399
|
-
}
|
|
400
|
-
return undefined;
|
|
401
|
-
},
|
|
402
|
-
}),
|
|
403
|
-
).trim(),
|
|
404
|
-
);
|
|
405
|
-
maxReconnectCycles = Number.isInteger(parsedCycles) && parsedCycles > 0 ? parsedCycles : 10;
|
|
406
|
-
}
|
|
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
|
+
);
|
|
407
388
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const raw = String(value ?? "").trim();
|
|
423
|
-
const num = Number(raw);
|
|
424
|
-
if (!raw) {
|
|
425
|
-
return "Required";
|
|
426
|
-
}
|
|
427
|
-
if (!Number.isInteger(num) || num < 1) {
|
|
428
|
-
return "Must be an integer >= 1";
|
|
429
|
-
}
|
|
430
|
-
return undefined;
|
|
431
|
-
},
|
|
432
|
-
}),
|
|
433
|
-
).trim(),
|
|
434
|
-
);
|
|
435
|
-
mediaMaxMb = Number.isInteger(parsedMediaMax) && parsedMediaMax > 0 ? parsedMediaMax : 20;
|
|
436
|
-
}
|
|
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
|
+
});
|
|
437
403
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
:
|
|
471
|
-
|
|
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
|
+
}
|
|
422
|
+
|
|
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
|
+
}
|
|
472
442
|
|
|
473
|
-
|
|
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({
|
|
474
499
|
cfg,
|
|
475
500
|
accountId,
|
|
476
|
-
input
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
robotCode,
|
|
480
|
-
corpId,
|
|
481
|
-
agentId,
|
|
482
|
-
dmPolicy: dmPolicyValue as "open" | "allowlist",
|
|
483
|
-
groupPolicy: groupPolicyValue as "open" | "allowlist" | "disabled",
|
|
484
|
-
allowFrom,
|
|
485
|
-
groupAllowFrom,
|
|
486
|
-
displayNameResolution: displayNameResolutionValue as "disabled" | "all",
|
|
487
|
-
mediaUrlAllowlist,
|
|
488
|
-
messageType,
|
|
489
|
-
cardTemplateId,
|
|
490
|
-
cardTemplateKey,
|
|
491
|
-
maxReconnectCycles,
|
|
492
|
-
mediaMaxMb,
|
|
493
|
-
journalTTLDays,
|
|
494
|
-
},
|
|
495
|
-
});
|
|
501
|
+
input,
|
|
502
|
+
}),
|
|
503
|
+
};
|
|
496
504
|
|
|
497
|
-
|
|
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
|
+
});
|
|
498
546
|
},
|
|
547
|
+
finalize: async ({ cfg, accountId, prompter }) => ({
|
|
548
|
+
cfg: await configureDingTalkAccount({
|
|
549
|
+
cfg,
|
|
550
|
+
accountId,
|
|
551
|
+
prompter,
|
|
552
|
+
}),
|
|
553
|
+
}),
|
|
499
554
|
};
|