@soimy/dingtalk 3.6.0 → 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.
@@ -11,8 +11,17 @@ export const DEFAULT_CREATED_AT_MATCH_WINDOW_MS = 2000;
11
11
  const MAX_RECORDS_PER_SCOPE = 1000;
12
12
 
13
13
  export type MessageContextDirection = "inbound" | "outbound";
14
- export type MessageAliasKind = "inboundMsgId" | "messageId" | "processQueryKey" | "outTrackId" | "cardInstanceId";
15
- export type MessageDeliveryKind = "session" | "proactive-text" | "proactive-card" | "proactive-media";
14
+ export type MessageAliasKind =
15
+ | "inboundMsgId"
16
+ | "messageId"
17
+ | "processQueryKey"
18
+ | "outTrackId"
19
+ | "cardInstanceId";
20
+ export type MessageDeliveryKind =
21
+ | "session"
22
+ | "proactive-text"
23
+ | "proactive-card"
24
+ | "proactive-media";
16
25
  export const DEFAULT_OUTBOUND_SENDER = {
17
26
  senderId: "bot",
18
27
  senderName: "OpenClaw",
@@ -47,6 +56,7 @@ export interface MessageRecord {
47
56
  quotedMessageId?: string;
48
57
  media?: {
49
58
  downloadCode?: string;
59
+ downloadCodes?: string[];
50
60
  spaceId?: string;
51
61
  fileId?: string;
52
62
  };
@@ -96,6 +106,7 @@ interface BaseUpsertParams {
96
106
  quotedMessageId?: string;
97
107
  media?: {
98
108
  downloadCode?: string;
109
+ downloadCodes?: string[];
99
110
  spaceId?: string;
100
111
  fileId?: string;
101
112
  };
@@ -120,7 +131,11 @@ interface ScopeParams {
120
131
  const stateCache = new Map<string, MessageContextState>();
121
132
 
122
133
  function getScopeKey(params: ScopeParams): string {
123
- return JSON.stringify([params.storePath || "__memory__", params.accountId, params.conversationId || null]);
134
+ return JSON.stringify([
135
+ params.storePath || "__memory__",
136
+ params.accountId,
137
+ params.conversationId || null,
138
+ ]);
124
139
  }
125
140
 
126
141
  function fallbackState(): MessageContextState {
@@ -145,19 +160,26 @@ function normalizeMedia(value: unknown): MessageRecord["media"] | undefined {
145
160
  if (!candidate) {
146
161
  return undefined;
147
162
  }
148
- const downloadCode = typeof candidate.downloadCode === "string" && candidate.downloadCode.trim()
149
- ? candidate.downloadCode.trim()
150
- : undefined;
151
- const spaceId = typeof candidate.spaceId === "string" && candidate.spaceId.trim()
152
- ? candidate.spaceId.trim()
153
- : undefined;
154
- const fileId = typeof candidate.fileId === "string" && candidate.fileId.trim()
155
- ? candidate.fileId.trim()
156
- : undefined;
157
- if (!downloadCode && !spaceId && !fileId) {
163
+ const downloadCode =
164
+ typeof candidate.downloadCode === "string" && candidate.downloadCode.trim()
165
+ ? candidate.downloadCode.trim()
166
+ : undefined;
167
+ const downloadCodes =
168
+ Array.isArray(candidate.downloadCodes) && candidate.downloadCodes.length > 0
169
+ ? candidate.downloadCodes.filter((c: unknown) => typeof c === "string" && c.trim())
170
+ : undefined;
171
+ const spaceId =
172
+ typeof candidate.spaceId === "string" && candidate.spaceId.trim()
173
+ ? candidate.spaceId.trim()
174
+ : undefined;
175
+ const fileId =
176
+ typeof candidate.fileId === "string" && candidate.fileId.trim()
177
+ ? candidate.fileId.trim()
178
+ : undefined;
179
+ if (!downloadCode && (!downloadCodes || downloadCodes.length === 0) && !spaceId && !fileId) {
158
180
  return undefined;
159
181
  }
160
- return { downloadCode, spaceId, fileId };
182
+ return { downloadCode, downloadCodes, spaceId, fileId };
161
183
  }
162
184
 
163
185
  function normalizeQuotedRef(value: unknown): QuotedRef | undefined {
@@ -180,7 +202,9 @@ function normalizeQuotedRef(value: unknown): QuotedRef | undefined {
180
202
  ? candidate.key
181
203
  : undefined;
182
204
  const valueString =
183
- typeof candidate.value === "string" && candidate.value.trim() ? candidate.value.trim() : undefined;
205
+ typeof candidate.value === "string" && candidate.value.trim()
206
+ ? candidate.value.trim()
207
+ : undefined;
184
208
  const fallbackCreatedAt =
185
209
  typeof candidate.fallbackCreatedAt === "number" && Number.isFinite(candidate.fallbackCreatedAt)
186
210
  ? candidate.fallbackCreatedAt
@@ -213,21 +237,26 @@ function normalizeDelivery(value: unknown): MessageRecord["delivery"] | undefine
213
237
  if (!candidate) {
214
238
  return undefined;
215
239
  }
216
- const messageId = typeof candidate.messageId === "string" && candidate.messageId.trim()
217
- ? candidate.messageId.trim()
218
- : undefined;
219
- const processQueryKey = typeof candidate.processQueryKey === "string" && candidate.processQueryKey.trim()
220
- ? candidate.processQueryKey.trim()
221
- : undefined;
222
- const outTrackId = typeof candidate.outTrackId === "string" && candidate.outTrackId.trim()
223
- ? candidate.outTrackId.trim()
224
- : undefined;
225
- const cardInstanceId = typeof candidate.cardInstanceId === "string" && candidate.cardInstanceId.trim()
226
- ? candidate.cardInstanceId.trim()
227
- : undefined;
228
- const kind = typeof candidate.kind === "string" && candidate.kind.trim()
229
- ? (candidate.kind.trim() as MessageDeliveryKind)
230
- : undefined;
240
+ const messageId =
241
+ typeof candidate.messageId === "string" && candidate.messageId.trim()
242
+ ? candidate.messageId.trim()
243
+ : undefined;
244
+ const processQueryKey =
245
+ typeof candidate.processQueryKey === "string" && candidate.processQueryKey.trim()
246
+ ? candidate.processQueryKey.trim()
247
+ : undefined;
248
+ const outTrackId =
249
+ typeof candidate.outTrackId === "string" && candidate.outTrackId.trim()
250
+ ? candidate.outTrackId.trim()
251
+ : undefined;
252
+ const cardInstanceId =
253
+ typeof candidate.cardInstanceId === "string" && candidate.cardInstanceId.trim()
254
+ ? candidate.cardInstanceId.trim()
255
+ : undefined;
256
+ const kind =
257
+ typeof candidate.kind === "string" && candidate.kind.trim()
258
+ ? (candidate.kind.trim() as MessageDeliveryKind)
259
+ : undefined;
231
260
  if (!messageId && !processQueryKey && !outTrackId && !cardInstanceId && !kind) {
232
261
  return undefined;
233
262
  }
@@ -248,30 +277,46 @@ function normalizeMessageRecord(value: unknown): MessageRecord | null {
248
277
  if (!candidate) {
249
278
  return null;
250
279
  }
251
- const msgId = typeof candidate.msgId === "string" && candidate.msgId.trim() ? candidate.msgId.trim() : "";
252
- const direction = candidate.direction === "outbound" ? "outbound" : candidate.direction === "inbound" ? "inbound" : null;
253
- const accountId = typeof candidate.accountId === "string" && candidate.accountId.trim()
254
- ? candidate.accountId.trim()
255
- : "";
256
- const conversationId = typeof candidate.conversationId === "string" && candidate.conversationId.trim()
257
- ? candidate.conversationId.trim()
258
- : null;
259
- const createdAt = typeof candidate.createdAt === "number" && Number.isFinite(candidate.createdAt)
260
- ? candidate.createdAt
261
- : NaN;
262
- const updatedAt = typeof candidate.updatedAt === "number" && Number.isFinite(candidate.updatedAt)
263
- ? candidate.updatedAt
264
- : Date.now();
280
+ const msgId =
281
+ typeof candidate.msgId === "string" && candidate.msgId.trim() ? candidate.msgId.trim() : "";
282
+ const direction =
283
+ candidate.direction === "outbound"
284
+ ? "outbound"
285
+ : candidate.direction === "inbound"
286
+ ? "inbound"
287
+ : null;
288
+ const accountId =
289
+ typeof candidate.accountId === "string" && candidate.accountId.trim()
290
+ ? candidate.accountId.trim()
291
+ : "";
292
+ const conversationId =
293
+ typeof candidate.conversationId === "string" && candidate.conversationId.trim()
294
+ ? candidate.conversationId.trim()
295
+ : null;
296
+ const createdAt =
297
+ typeof candidate.createdAt === "number" && Number.isFinite(candidate.createdAt)
298
+ ? candidate.createdAt
299
+ : NaN;
300
+ const updatedAt =
301
+ typeof candidate.updatedAt === "number" && Number.isFinite(candidate.updatedAt)
302
+ ? candidate.updatedAt
303
+ : Date.now();
265
304
  if (!msgId || !direction || !accountId || !Number.isFinite(createdAt)) {
266
305
  return null;
267
306
  }
268
- const expiresAt = typeof candidate.expiresAt === "number" && Number.isFinite(candidate.expiresAt)
269
- ? candidate.expiresAt
270
- : undefined;
307
+ const expiresAt =
308
+ typeof candidate.expiresAt === "number" && Number.isFinite(candidate.expiresAt)
309
+ ? candidate.expiresAt
310
+ : undefined;
271
311
  return {
272
312
  msgId,
273
313
  direction,
274
- topic: candidate.topic === null ? null : typeof candidate.topic === "string" ? candidate.topic : null,
314
+ topic:
315
+ candidate.topic === null
316
+ ? null
317
+ : typeof candidate.topic === "string"
318
+ ? candidate.topic
319
+ : null,
275
320
  accountId,
276
321
  conversationId,
277
322
  createdAt,
@@ -279,16 +324,26 @@ function normalizeMessageRecord(value: unknown): MessageRecord | null {
279
324
  expiresAt,
280
325
  messageType: typeof candidate.messageType === "string" ? candidate.messageType : undefined,
281
326
  text: typeof candidate.text === "string" ? candidate.text : undefined,
282
- attachmentText: typeof candidate.attachmentText === "string" ? candidate.attachmentText : undefined,
327
+ attachmentText:
328
+ typeof candidate.attachmentText === "string" ? candidate.attachmentText : undefined,
283
329
  attachmentTextSource: normalizeAttachmentTextSource(candidate.attachmentTextSource),
284
330
  attachmentTextTruncated: candidate.attachmentTextTruncated === true ? true : undefined,
285
331
  attachmentFileName:
286
332
  typeof candidate.attachmentFileName === "string" ? candidate.attachmentFileName : undefined,
287
333
  quotedRef: normalizeQuotedRef(candidate.quotedRef),
288
- senderId: typeof candidate.senderId === "string" && candidate.senderId.trim() ? candidate.senderId.trim() : undefined,
289
- senderName: typeof candidate.senderName === "string" && candidate.senderName.trim() ? candidate.senderName.trim() : undefined,
334
+ senderId:
335
+ typeof candidate.senderId === "string" && candidate.senderId.trim()
336
+ ? candidate.senderId.trim()
337
+ : undefined,
338
+ senderName:
339
+ typeof candidate.senderName === "string" && candidate.senderName.trim()
340
+ ? candidate.senderName.trim()
341
+ : undefined,
290
342
  mentions: normalizeMentions(candidate.mentions),
291
- chatType: candidate.chatType === "direct" || candidate.chatType === "group" ? candidate.chatType : undefined,
343
+ chatType:
344
+ candidate.chatType === "direct" || candidate.chatType === "group"
345
+ ? candidate.chatType
346
+ : undefined,
292
347
  quotedMessageId:
293
348
  typeof candidate.quotedMessageId === "string" && candidate.quotedMessageId.trim()
294
349
  ? candidate.quotedMessageId.trim()
@@ -327,10 +382,17 @@ function buildAliasEntries(record: MessageRecord): Array<[string, string]> {
327
382
  }
328
383
 
329
384
  function isRecordExpired(record: MessageRecord, nowMs: number): boolean {
330
- return typeof record.expiresAt === "number" && Number.isFinite(record.expiresAt) && nowMs >= record.expiresAt;
385
+ return (
386
+ typeof record.expiresAt === "number" &&
387
+ Number.isFinite(record.expiresAt) &&
388
+ nowMs >= record.expiresAt
389
+ );
331
390
  }
332
391
 
333
- function normalizeState(state: MessageContextState, nowMs: number): { state: MessageContextState; removed: number } {
392
+ function normalizeState(
393
+ state: MessageContextState,
394
+ nowMs: number,
395
+ ): { state: MessageContextState; removed: number } {
334
396
  const normalizedRecords: Record<string, MessageRecord> = {};
335
397
  const sortedRecords = Object.values(state.records)
336
398
  .map((record) => normalizeMessageRecord(record))
@@ -363,12 +425,15 @@ function hydrateState(params: ScopeParams, nowMs: number): MessageContextState {
363
425
  if (!params.storePath) {
364
426
  return fallbackState();
365
427
  }
366
- const persisted = readNamespaceJson<Partial<PersistedMessageContextState>>(MESSAGE_CONTEXT_NAMESPACE, {
367
- storePath: params.storePath,
368
- scope: { accountId: params.accountId, conversationId: params.conversationId || undefined },
369
- format: "json",
370
- fallback: { version: MESSAGE_CONTEXT_VERSION, updatedAt: Date.now(), records: {} },
371
- });
428
+ const persisted = readNamespaceJson<Partial<PersistedMessageContextState>>(
429
+ MESSAGE_CONTEXT_NAMESPACE,
430
+ {
431
+ storePath: params.storePath,
432
+ scope: { accountId: params.accountId, conversationId: params.conversationId || undefined },
433
+ format: "json",
434
+ fallback: { version: MESSAGE_CONTEXT_VERSION, updatedAt: Date.now(), records: {} },
435
+ },
436
+ );
372
437
  const parsedRecords = asRecord(persisted.records) || {};
373
438
  const hydrated = fallbackState();
374
439
  hydrated.updatedAt = typeof persisted.updatedAt === "number" ? persisted.updatedAt : Date.now();
@@ -426,14 +491,20 @@ function mergeText(existing: string | undefined, next: string | undefined): stri
426
491
  return next;
427
492
  }
428
493
 
429
- function mergeAttachmentText(existing: string | undefined, next: string | undefined): string | undefined {
494
+ function mergeAttachmentText(
495
+ existing: string | undefined,
496
+ next: string | undefined,
497
+ ): string | undefined {
430
498
  if (typeof next !== "string") {
431
499
  return existing;
432
500
  }
433
501
  return next;
434
502
  }
435
503
 
436
- function mergeQuotedRef(existing: QuotedRef | undefined, next: QuotedRef | undefined): QuotedRef | undefined {
504
+ function mergeQuotedRef(
505
+ existing: QuotedRef | undefined,
506
+ next: QuotedRef | undefined,
507
+ ): QuotedRef | undefined {
437
508
  if (!existing) {
438
509
  return next;
439
510
  }
@@ -448,14 +519,20 @@ function mergeQuotedRef(existing: QuotedRef | undefined, next: QuotedRef | undef
448
519
  };
449
520
  }
450
521
 
451
- function mergeStringField(existing: string | undefined, next: string | undefined): string | undefined {
522
+ function mergeStringField(
523
+ existing: string | undefined,
524
+ next: string | undefined,
525
+ ): string | undefined {
452
526
  if (typeof next !== "string" || !next.trim()) {
453
527
  return existing;
454
528
  }
455
529
  return next.trim();
456
530
  }
457
531
 
458
- function mergeMentions(existing: string[] | undefined, next: string[] | undefined): string[] | undefined {
532
+ function mergeMentions(
533
+ existing: string[] | undefined,
534
+ next: string[] | undefined,
535
+ ): string[] | undefined {
459
536
  if (!next) {
460
537
  return existing;
461
538
  }
@@ -474,6 +551,7 @@ function mergeMedia(
474
551
  }
475
552
  return {
476
553
  downloadCode: next.downloadCode || existing.downloadCode,
554
+ downloadCodes: next.downloadCodes || existing.downloadCodes,
477
555
  spaceId: next.spaceId || existing.spaceId,
478
556
  fileId: next.fileId || existing.fileId,
479
557
  };
@@ -512,7 +590,10 @@ function mergeAttachmentTextTruncated(
512
590
  return next === undefined ? existing : next;
513
591
  }
514
592
 
515
- function mergeAttachmentFileName(existing: string | undefined, next: string | undefined): string | undefined {
593
+ function mergeAttachmentFileName(
594
+ existing: string | undefined,
595
+ next: string | undefined,
596
+ ): string | undefined {
516
597
  if (typeof next !== "string") {
517
598
  return existing;
518
599
  }
@@ -521,7 +602,11 @@ function mergeAttachmentFileName(existing: string | undefined, next: string | un
521
602
 
522
603
  function resolveExistingMsgId(
523
604
  state: MessageContextState,
524
- params: { direction: MessageContextDirection; msgId?: string; delivery?: MessageRecord["delivery"] },
605
+ params: {
606
+ direction: MessageContextDirection;
607
+ msgId?: string;
608
+ delivery?: MessageRecord["delivery"];
609
+ },
525
610
  nowMs: number,
526
611
  ): string | undefined {
527
612
  if (params.direction === "inbound" && params.msgId) {
@@ -556,11 +641,19 @@ function resolveExistingMsgId(
556
641
  return undefined;
557
642
  }
558
643
 
559
- function computeExpiresAt(nowMs: number, ttlMs?: number, ttlReferenceMs?: number): number | undefined {
644
+ function computeExpiresAt(
645
+ nowMs: number,
646
+ ttlMs?: number,
647
+ ttlReferenceMs?: number,
648
+ ): number | undefined {
560
649
  if (typeof ttlMs !== "number" || !Number.isFinite(ttlMs) || ttlMs <= 0) {
561
650
  return undefined;
562
651
  }
563
- return (typeof ttlReferenceMs === "number" && Number.isFinite(ttlReferenceMs) ? ttlReferenceMs : nowMs) + ttlMs;
652
+ return (
653
+ (typeof ttlReferenceMs === "number" && Number.isFinite(ttlReferenceMs)
654
+ ? ttlReferenceMs
655
+ : nowMs) + ttlMs
656
+ );
564
657
  }
565
658
 
566
659
  function pruneStateByCreatedAt(
@@ -622,11 +715,15 @@ function upsertRecord(
622
715
  if (params.cleanupCreatedAtTtlDays && params.cleanupCreatedAtTtlDays > 0) {
623
716
  state = pruneStateByCreatedAt(state, params.cleanupCreatedAtTtlDays, nowMs).state;
624
717
  }
625
- const existingMsgId = resolveExistingMsgId(state, {
626
- direction: params.direction,
627
- msgId: params.msgId,
628
- delivery: params.delivery,
629
- }, nowMs);
718
+ const existingMsgId = resolveExistingMsgId(
719
+ state,
720
+ {
721
+ direction: params.direction,
722
+ msgId: params.msgId,
723
+ delivery: params.delivery,
724
+ },
725
+ nowMs,
726
+ );
630
727
  const canonicalMsgId =
631
728
  existingMsgId ||
632
729
  params.msgId ||
@@ -648,9 +745,7 @@ function upsertRecord(
648
745
  createdAt: existing?.createdAt ?? params.createdAt,
649
746
  updatedAt: nowMs,
650
747
  expiresAt:
651
- expiresAt === undefined
652
- ? existing?.expiresAt
653
- : Math.max(expiresAt, existing?.expiresAt ?? 0),
748
+ expiresAt === undefined ? existing?.expiresAt : Math.max(expiresAt, existing?.expiresAt ?? 0),
654
749
  messageType: params.messageType || existing?.messageType,
655
750
  text: mergeText(existing?.text, params.text),
656
751
  attachmentText: mergeAttachmentText(existing?.attachmentText, params.attachmentText),
@@ -692,7 +787,9 @@ export function upsertInboundMessageContext(params: UpsertInboundMessageContextP
692
787
  );
693
788
  }
694
789
 
695
- export function upsertOutboundMessageContext(params: UpsertOutboundMessageContextParams): string | undefined {
790
+ export function upsertOutboundMessageContext(
791
+ params: UpsertOutboundMessageContextParams,
792
+ ): string | undefined {
696
793
  return upsertRecord({
697
794
  ...params,
698
795
  direction: "outbound",
@@ -780,7 +877,10 @@ export function resolveByQuotedRef(
780
877
  `[DingTalk][QuotedRef] Resolve outbound by ${quotedRef.key}=${quotedRef.value} hit=no accountId=${params.accountId} conversationId=${params.conversationId || "(none)"}`,
781
878
  );
782
879
  }
783
- if (typeof quotedRef.fallbackCreatedAt === "number" && Number.isFinite(quotedRef.fallbackCreatedAt)) {
880
+ if (
881
+ typeof quotedRef.fallbackCreatedAt === "number" &&
882
+ Number.isFinite(quotedRef.fallbackCreatedAt)
883
+ ) {
784
884
  const record = resolveByCreatedAtWindow({
785
885
  ...params,
786
886
  createdAt: quotedRef.fallbackCreatedAt,
@@ -828,9 +928,7 @@ export function resolveByCreatedAtWindow(
828
928
  return bestRecord;
829
929
  }
830
930
 
831
- export function cleanupExpiredMessageContexts(
832
- params: ScopeParams & { nowMs?: number },
833
- ): number {
931
+ export function cleanupExpiredMessageContexts(params: ScopeParams & { nowMs?: number }): number {
834
932
  const nowMs = params.nowMs ?? Date.now();
835
933
  const state = loadState(params, nowMs);
836
934
  const beforeCount = Object.keys(state.records).length;
@@ -850,12 +948,12 @@ export function clearMessageContextCacheForTest(): void {
850
948
  /**
851
949
  * Lists non-expired message-context records for one account/conversation scope in createdAt ascending order.
852
950
  */
853
- export function listMessageContexts(
854
- params: ScopeParams & { nowMs?: number },
855
- ): MessageRecord[] {
951
+ export function listMessageContexts(params: ScopeParams & { nowMs?: number }): MessageRecord[] {
856
952
  const nowMs = params.nowMs ?? Date.now();
857
953
  const state = loadState(params, nowMs);
858
954
  return state.recentByCreatedAt
859
955
  .map((msgId) => state.records[msgId])
860
- .filter((record): record is MessageRecord => Boolean(record) && !isRecordExpired(record, nowMs));
956
+ .filter(
957
+ (record): record is MessageRecord => Boolean(record) && !isRecordExpired(record, nowMs),
958
+ );
861
959
  }
@@ -6,6 +6,7 @@ import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
6
6
  import { getConfig, stripTargetPrefix } from "../config";
7
7
  import { getLogger } from "../logger-context";
8
8
  import { resolveOriginalPeerId } from "../peer-id-registry";
9
+ import { hasConfiguredSecretInput } from "../secret-input";
9
10
  import { sendMedia, sendMessage } from "../send-service";
10
11
  import { parseBooleanLike } from "../utils";
11
12
 
@@ -50,7 +51,7 @@ function inferCardOwnerIdFromTarget(target: string): string | undefined {
50
51
 
51
52
  function describeDingTalkMessageTool(cfg: OpenClawConfig) {
52
53
  const config = getConfig(cfg);
53
- const configured = Boolean(config.clientId && config.clientSecret);
54
+ const configured = Boolean(config.clientId && hasConfiguredSecretInput(config.clientSecret));
54
55
  if (!configured && !(config.accounts && Object.keys(config.accounts).length > 0)) {
55
56
  return { actions: [], capabilities: [], schema: null };
56
57
  }
package/src/onboarding.ts CHANGED
@@ -12,12 +12,17 @@ import {
12
12
  openUrlInBrowser,
13
13
  RegistrationError,
14
14
  } from "./device-registration.js";
15
+ import {
16
+ hasConfiguredSecretInput,
17
+ normalizeSecretInputString,
18
+ parseSecretInputString,
19
+ } from "./secret-input.js";
15
20
  import type { DingTalkConfig, DingTalkChannelConfig } from "./types.js";
16
21
 
17
22
  const channel = "dingtalk" as const;
18
23
 
19
24
  function isConfigured(account: DingTalkConfig): boolean {
20
- return Boolean(account.clientId && account.clientSecret);
25
+ return Boolean(account.clientId && hasConfiguredSecretInput(account.clientSecret));
21
26
  }
22
27
 
23
28
  function applyAccountNameToChannelSection(params: {
@@ -354,7 +359,9 @@ function applyGenericSetupInput(params: {
354
359
  name: params.input.name,
355
360
  clientId: typeof params.input.token === "string" ? params.input.token.trim() : undefined,
356
361
  clientSecret:
357
- typeof params.input.password === "string" ? params.input.password.trim() : undefined,
362
+ typeof params.input.password === "string"
363
+ ? parseSecretInputString(params.input.password)
364
+ : undefined,
358
365
  },
359
366
  });
360
367
  }
@@ -368,7 +375,9 @@ async function configureDingTalkAccount(params: {
368
375
  const resolved = resolveDingTalkAccount(cfg, accountId);
369
376
 
370
377
  // ── Credential acquisition: auto-register or manual ────────────────────
371
- const hasExistingCredentials = Boolean(resolved.clientId && resolved.clientSecret);
378
+ const hasExistingCredentials = Boolean(
379
+ resolved.clientId && hasConfiguredSecretInput(resolved.clientSecret),
380
+ );
372
381
  const credentialMethod = await prompter.select({
373
382
  message: "How do you want to get DingTalk bot credentials?",
374
383
  options: [
@@ -441,7 +450,7 @@ async function configureDingTalkAccount(params: {
441
450
  await prompter.text({
442
451
  message: "Client Secret (AppSecret)",
443
452
  placeholder: "xxx-xxx-xxx-xxx",
444
- initialValue: resolved.clientSecret ?? undefined,
453
+ initialValue: normalizeSecretInputString(resolved.clientSecret),
445
454
  validate: (value: string) => (String(value ?? "").trim() ? undefined : "Required"),
446
455
  }),
447
456
  ).trim();
@@ -461,7 +470,7 @@ async function configureDingTalkAccount(params: {
461
470
  await prompter.text({
462
471
  message: "Client Secret (AppSecret)",
463
472
  placeholder: "xxx-xxx-xxx-xxx",
464
- initialValue: resolved.clientSecret ?? undefined,
473
+ initialValue: normalizeSecretInputString(resolved.clientSecret),
465
474
  validate: (value: string) => (String(value ?? "").trim() ? undefined : "Required"),
466
475
  }),
467
476
  ).trim();
@@ -523,7 +532,7 @@ async function configureDingTalkAccount(params: {
523
532
  accountId,
524
533
  input: {
525
534
  clientId: String(clientId).trim(),
526
- clientSecret: String(clientSecret).trim(),
535
+ clientSecret: parseSecretInputString(clientSecret),
527
536
  dmPolicy: dmPolicyValue,
528
537
  groupPolicy: groupPolicyValue,
529
538
  messageType,
@@ -0,0 +1,49 @@
1
+ import * as os from "node:os";
2
+ import * as path from "node:path";
3
+
4
+ const WINDOWS_ROOT_DIRECTORIES = new Set([
5
+ "Users",
6
+ "Program Files",
7
+ "Program Files (x86)",
8
+ "ProgramData",
9
+ "Windows",
10
+ "Documents and Settings",
11
+ ]);
12
+
13
+ export function resolveRelativePath(input: string): string {
14
+ const trimmed = input.trim();
15
+ if (!trimmed) {
16
+ return trimmed;
17
+ }
18
+
19
+ const segments = (value: string): string[] => value.split(/[\\/]+/).filter(Boolean);
20
+ const pathSegments = segments(trimmed);
21
+ const firstSegment = pathSegments[0];
22
+
23
+ if (trimmed === "~") {
24
+ return path.resolve(os.homedir());
25
+ }
26
+ if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
27
+ return path.resolve(os.homedir(), ...segments(trimmed.slice(2)));
28
+ }
29
+
30
+ if (process.platform === "win32") {
31
+ if (/^[a-zA-Z]:[\\/]/.test(trimmed)) {
32
+ return path.win32.normalize(trimmed);
33
+ }
34
+ if (firstSegment && /^[a-zA-Z]:$/.test(firstSegment)) {
35
+ return path.win32.resolve(`${firstSegment}\\`, ...pathSegments.slice(1));
36
+ }
37
+ if (firstSegment && WINDOWS_ROOT_DIRECTORIES.has(firstSegment)) {
38
+ return path.win32.resolve("\\", ...pathSegments);
39
+ }
40
+ }
41
+
42
+ if (/^[\\/]/.test(trimmed)) {
43
+ return path.resolve(path.sep, ...pathSegments);
44
+ }
45
+
46
+ return path.resolve(process.cwd(), ...pathSegments);
47
+ }
48
+
49
+ export const resolveUserPath = resolveRelativePath;