@openclaw-china/dingtalk 2026.3.7 → 2026.3.8-2

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/dist/index.js CHANGED
@@ -4215,13 +4215,24 @@ var coerce = {
4215
4215
  date: ((arg) => ZodDate.create({ ...arg, coerce: true }))
4216
4216
  };
4217
4217
  var NEVER = INVALID;
4218
- var DingtalkConfigSchema = external_exports.object({
4218
+ function toTrimmedString(value) {
4219
+ if (value === void 0 || value === null) return void 0;
4220
+ const next = String(value).trim();
4221
+ return next ? next : void 0;
4222
+ }
4223
+ var optionalCoercedString = external_exports.preprocess(
4224
+ (value) => toTrimmedString(value),
4225
+ external_exports.string().min(1).optional()
4226
+ );
4227
+ var DingtalkAccountSchema = external_exports.object({
4228
+ /** 账户显示名 */
4229
+ name: external_exports.string().optional(),
4219
4230
  /** 是否启用钉钉渠道 */
4220
4231
  enabled: external_exports.boolean().optional().default(true),
4221
4232
  /** 钉钉应用 AppKey (clientId) */
4222
- clientId: external_exports.string().optional(),
4233
+ clientId: optionalCoercedString,
4223
4234
  /** 钉钉应用 AppSecret (clientSecret) */
4224
- clientSecret: external_exports.string().optional(),
4235
+ clientSecret: optionalCoercedString,
4225
4236
  /** 单聊策略: open=开放, pairing=配对, allowlist=白名单 */
4226
4237
  dmPolicy: external_exports.enum(["open", "pairing", "allowlist"]).optional().default("open"),
4227
4238
  /** 群聊策略: open=开放, allowlist=白名单, disabled=禁用 */
@@ -4252,6 +4263,29 @@ var DingtalkConfigSchema = external_exports.object({
4252
4263
  keepDays: external_exports.number().optional()
4253
4264
  }).optional()
4254
4265
  });
4266
+ DingtalkAccountSchema.extend({
4267
+ defaultAccount: external_exports.string().optional(),
4268
+ accounts: external_exports.record(DingtalkAccountSchema).optional()
4269
+ });
4270
+ var DEFAULT_ACCOUNT_ID = "default";
4271
+ var DINGTALK_ACCOUNT_KEYS = [
4272
+ "name",
4273
+ "clientId",
4274
+ "clientSecret",
4275
+ "dmPolicy",
4276
+ "groupPolicy",
4277
+ "requireMention",
4278
+ "allowFrom",
4279
+ "groupAllowFrom",
4280
+ "historyLimit",
4281
+ "textChunkLimit",
4282
+ "longTaskNoticeDelayMs",
4283
+ "enableAICard",
4284
+ "gatewayToken",
4285
+ "gatewayPassword",
4286
+ "maxFileSizeMB",
4287
+ "inboundMedia"
4288
+ ];
4255
4289
  var DEFAULT_INBOUND_MEDIA_DIR = join(homedir(), ".openclaw", "media", "dingtalk", "inbound");
4256
4290
  var DEFAULT_INBOUND_MEDIA_KEEP_DAYS = 7;
4257
4291
  var DEFAULT_INBOUND_MEDIA_TEMP_DIR = join(tmpdir(), "dingtalk-media");
@@ -4265,16 +4299,116 @@ function resolveInboundMediaKeepDays(config) {
4265
4299
  function resolveInboundMediaTempDir() {
4266
4300
  return DEFAULT_INBOUND_MEDIA_TEMP_DIR;
4267
4301
  }
4302
+ function cloneIfObject(value) {
4303
+ if (value && typeof value === "object") {
4304
+ return structuredClone(value);
4305
+ }
4306
+ return value;
4307
+ }
4308
+ function baseLooksLikeConcreteAccount(cfg) {
4309
+ if (!cfg) return false;
4310
+ return Boolean(
4311
+ toTrimmedString(cfg.clientId) || toTrimmedString(cfg.clientSecret) || toTrimmedString(cfg.gatewayToken) || toTrimmedString(cfg.gatewayPassword) || toTrimmedString(cfg.name)
4312
+ );
4313
+ }
4314
+ function listConfiguredAccountIds(cfg) {
4315
+ const accounts = cfg.channels?.dingtalk?.accounts;
4316
+ if (!accounts || typeof accounts !== "object") return [];
4317
+ return Object.keys(accounts).filter(Boolean);
4318
+ }
4319
+ function listDingtalkAccountIds(cfg) {
4320
+ const ids = new Set(listConfiguredAccountIds(cfg));
4321
+ if (ids.size === 0) return [DEFAULT_ACCOUNT_ID];
4322
+ if (baseLooksLikeConcreteAccount(cfg.channels?.dingtalk) && !ids.has(DEFAULT_ACCOUNT_ID)) {
4323
+ ids.add(DEFAULT_ACCOUNT_ID);
4324
+ }
4325
+ return Array.from(ids).sort((a, b) => a.localeCompare(b));
4326
+ }
4327
+ function resolveDefaultDingtalkAccountId(cfg) {
4328
+ const dingtalkConfig = cfg.channels?.dingtalk;
4329
+ const preferred = toTrimmedString(dingtalkConfig?.defaultAccount);
4330
+ if (preferred && listDingtalkAccountIds(cfg).includes(preferred)) {
4331
+ return preferred;
4332
+ }
4333
+ const ids = listDingtalkAccountIds(cfg);
4334
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
4335
+ return ids[0] ?? DEFAULT_ACCOUNT_ID;
4336
+ }
4337
+ function resolveDingtalkAccountId(cfg, rawAccountId) {
4338
+ return toTrimmedString(rawAccountId) ?? resolveDefaultDingtalkAccountId(cfg);
4339
+ }
4340
+ function resolveAccountConfig(cfg, accountId) {
4341
+ const accounts = cfg.channels?.dingtalk?.accounts;
4342
+ if (!accounts || typeof accounts !== "object") return void 0;
4343
+ return accounts[accountId];
4344
+ }
4345
+ function extractBaseAccountPatch(cfg) {
4346
+ const patch = {};
4347
+ const patchRecord = patch;
4348
+ if (!cfg) return patch;
4349
+ for (const key of DINGTALK_ACCOUNT_KEYS) {
4350
+ const value = cfg[key];
4351
+ if (value !== void 0) {
4352
+ patchRecord[key] = cloneIfObject(value);
4353
+ }
4354
+ }
4355
+ return patch;
4356
+ }
4357
+ function moveDingtalkSingleAccountConfigToDefaultAccount(cfg) {
4358
+ const dingtalkConfig = cfg.channels?.dingtalk;
4359
+ if (!dingtalkConfig) {
4360
+ return cfg;
4361
+ }
4362
+ const accounts = dingtalkConfig.accounts ?? {};
4363
+ if (accounts[DEFAULT_ACCOUNT_ID]) {
4364
+ return cfg;
4365
+ }
4366
+ if (!baseLooksLikeConcreteAccount(dingtalkConfig)) {
4367
+ return cfg;
4368
+ }
4369
+ const patch = extractBaseAccountPatch(dingtalkConfig);
4370
+ if (Object.keys(patch).length === 0) {
4371
+ return cfg;
4372
+ }
4373
+ const nextChannel = { ...dingtalkConfig };
4374
+ for (const key of DINGTALK_ACCOUNT_KEYS) {
4375
+ delete nextChannel[key];
4376
+ }
4377
+ return {
4378
+ ...cfg,
4379
+ channels: {
4380
+ ...cfg.channels,
4381
+ dingtalk: {
4382
+ ...nextChannel,
4383
+ accounts: {
4384
+ ...accounts,
4385
+ [DEFAULT_ACCOUNT_ID]: {
4386
+ ...patch
4387
+ }
4388
+ }
4389
+ }
4390
+ }
4391
+ };
4392
+ }
4393
+ function mergeDingtalkAccountConfig(cfg, accountId) {
4394
+ const base = cfg.channels?.dingtalk ?? {};
4395
+ const { accounts: _ignored, defaultAccount: _ignored2, ...baseConfig } = base;
4396
+ const account = resolveAccountConfig(cfg, accountId) ?? {};
4397
+ return { ...baseConfig, ...account };
4398
+ }
4268
4399
  function isConfigured(config) {
4269
- return Boolean(config?.clientId && config?.clientSecret);
4400
+ const credentials = resolveDingtalkCredentials(config);
4401
+ return Boolean(credentials);
4270
4402
  }
4271
4403
  function resolveDingtalkCredentials(config) {
4272
- if (!config?.clientId || !config?.clientSecret) {
4404
+ const clientId = toTrimmedString(config?.clientId);
4405
+ const clientSecret = toTrimmedString(config?.clientSecret);
4406
+ if (!clientId || !clientSecret) {
4273
4407
  return void 0;
4274
4408
  }
4275
4409
  return {
4276
- clientId: config.clientId,
4277
- clientSecret: config.clientSecret
4410
+ clientId,
4411
+ clientSecret
4278
4412
  };
4279
4413
  }
4280
4414
  function createDingtalkClient(opts) {
@@ -5291,7 +5425,20 @@ async function finalizeInboundMediaFile(options) {
5291
5425
  await fsPromises.mkdir(datedDir, { recursive: true });
5292
5426
  await fsPromises.rename(current, target);
5293
5427
  return target;
5294
- } catch {
5428
+ } catch (error) {
5429
+ const code = error?.code ?? "";
5430
+ if (code === "EXDEV") {
5431
+ try {
5432
+ await fsPromises.copyFile(current, target);
5433
+ try {
5434
+ await fsPromises.unlink(current);
5435
+ } catch {
5436
+ }
5437
+ return target;
5438
+ } catch {
5439
+ return current;
5440
+ }
5441
+ }
5295
5442
  return current;
5296
5443
  }
5297
5444
  }
@@ -6370,7 +6517,7 @@ function isCommandLike(value) {
6370
6517
  }
6371
6518
  return typeof value.command === "function" && typeof value.description === "function" && typeof value.action === "function";
6372
6519
  }
6373
- function toTrimmedString(value) {
6520
+ function toTrimmedString2(value) {
6374
6521
  if (typeof value !== "string") {
6375
6522
  return void 0;
6376
6523
  }
@@ -6403,7 +6550,7 @@ function getPreferredAccountConfig(channelCfg) {
6403
6550
  if (!isRecord(accounts)) {
6404
6551
  return void 0;
6405
6552
  }
6406
- const defaultAccountId = toTrimmedString(channelCfg.defaultAccount);
6553
+ const defaultAccountId = toTrimmedString2(channelCfg.defaultAccount);
6407
6554
  if (defaultAccountId) {
6408
6555
  const preferred = accounts[defaultAccountId];
6409
6556
  if (isRecord(preferred)) {
@@ -6540,12 +6687,12 @@ async function configureDingtalk(prompter, cfg) {
6540
6687
  const existing = getChannelConfig(cfg, "dingtalk");
6541
6688
  const clientId = await prompter.askText({
6542
6689
  label: "DingTalk clientId\uFF08AppKey\uFF09",
6543
- defaultValue: toTrimmedString(existing.clientId),
6690
+ defaultValue: toTrimmedString2(existing.clientId),
6544
6691
  required: true
6545
6692
  });
6546
6693
  const clientSecret = await prompter.askSecret({
6547
6694
  label: "DingTalk clientSecret\uFF08AppSecret\uFF09",
6548
- existingValue: toTrimmedString(existing.clientSecret),
6695
+ existingValue: toTrimmedString2(existing.clientSecret),
6549
6696
  required: true
6550
6697
  });
6551
6698
  const enableAICard = await prompter.askConfirm(
@@ -6564,12 +6711,12 @@ async function configureFeishu(prompter, cfg) {
6564
6711
  const existing = getChannelConfig(cfg, "feishu-china");
6565
6712
  const appId = await prompter.askText({
6566
6713
  label: "Feishu appId",
6567
- defaultValue: toTrimmedString(existing.appId),
6714
+ defaultValue: toTrimmedString2(existing.appId),
6568
6715
  required: true
6569
6716
  });
6570
6717
  const appSecret = await prompter.askSecret({
6571
6718
  label: "Feishu appSecret",
6572
- existingValue: toTrimmedString(existing.appSecret),
6719
+ existingValue: toTrimmedString2(existing.appSecret),
6573
6720
  required: true
6574
6721
  });
6575
6722
  const sendMarkdownAsCard = await prompter.askConfirm(
@@ -6588,17 +6735,17 @@ async function configureWecom(prompter, cfg) {
6588
6735
  const existing = getChannelConfig(cfg, "wecom");
6589
6736
  const webhookPath = await prompter.askText({
6590
6737
  label: "Webhook \u8DEF\u5F84\uFF08\u9700\u4E0E\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u914D\u7F6E\u4E00\u81F4\uFF0C\u9ED8\u8BA4 /wecom\uFF09",
6591
- defaultValue: toTrimmedString(existing.webhookPath) ?? "/wecom",
6738
+ defaultValue: toTrimmedString2(existing.webhookPath) ?? "/wecom",
6592
6739
  required: true
6593
6740
  });
6594
6741
  const token = await prompter.askSecret({
6595
6742
  label: "WeCom token",
6596
- existingValue: toTrimmedString(existing.token),
6743
+ existingValue: toTrimmedString2(existing.token),
6597
6744
  required: true
6598
6745
  });
6599
6746
  const encodingAESKey = await prompter.askSecret({
6600
6747
  label: "WeCom encodingAESKey",
6601
- existingValue: toTrimmedString(existing.encodingAESKey),
6748
+ existingValue: toTrimmedString2(existing.encodingAESKey),
6602
6749
  required: true
6603
6750
  });
6604
6751
  return mergeChannelConfig(cfg, "wecom", {
@@ -6614,17 +6761,17 @@ async function configureWecomApp(prompter, cfg) {
6614
6761
  const existingAsr = isRecord(existing.asr) ? existing.asr : {};
6615
6762
  const webhookPath = await prompter.askText({
6616
6763
  label: "Webhook \u8DEF\u5F84\uFF08\u9700\u4E0E\u4F01\u4E1A\u5FAE\u4FE1\u540E\u53F0\u914D\u7F6E\u4E00\u81F4\uFF0C\u9ED8\u8BA4 /wecom-app\uFF09",
6617
- defaultValue: toTrimmedString(existing.webhookPath) ?? "/wecom-app",
6764
+ defaultValue: toTrimmedString2(existing.webhookPath) ?? "/wecom-app",
6618
6765
  required: true
6619
6766
  });
6620
6767
  const token = await prompter.askSecret({
6621
6768
  label: "WeCom App token",
6622
- existingValue: toTrimmedString(existing.token),
6769
+ existingValue: toTrimmedString2(existing.token),
6623
6770
  required: true
6624
6771
  });
6625
6772
  const encodingAESKey = await prompter.askSecret({
6626
6773
  label: "WeCom App encodingAESKey",
6627
- existingValue: toTrimmedString(existing.encodingAESKey),
6774
+ existingValue: toTrimmedString2(existing.encodingAESKey),
6628
6775
  required: true
6629
6776
  });
6630
6777
  const patch = {
@@ -6634,12 +6781,12 @@ async function configureWecomApp(prompter, cfg) {
6634
6781
  };
6635
6782
  const corpId = await prompter.askText({
6636
6783
  label: "corpId",
6637
- defaultValue: toTrimmedString(existing.corpId),
6784
+ defaultValue: toTrimmedString2(existing.corpId),
6638
6785
  required: true
6639
6786
  });
6640
6787
  const corpSecret = await prompter.askSecret({
6641
6788
  label: "corpSecret",
6642
- existingValue: toTrimmedString(existing.corpSecret),
6789
+ existingValue: toTrimmedString2(existing.corpSecret),
6643
6790
  required: true
6644
6791
  });
6645
6792
  const agentId = await prompter.askNumber({
@@ -6667,17 +6814,17 @@ async function configureWecomApp(prompter, cfg) {
6667
6814
  );
6668
6815
  asr.appId = await prompter.askText({
6669
6816
  label: "ASR appId\uFF08\u817E\u8BAF\u4E91\uFF09",
6670
- defaultValue: toTrimmedString(existingAsr.appId),
6817
+ defaultValue: toTrimmedString2(existingAsr.appId),
6671
6818
  required: true
6672
6819
  });
6673
6820
  asr.secretId = await prompter.askSecret({
6674
6821
  label: "ASR secretId\uFF08\u817E\u8BAF\u4E91\uFF09",
6675
- existingValue: toTrimmedString(existingAsr.secretId),
6822
+ existingValue: toTrimmedString2(existingAsr.secretId),
6676
6823
  required: true
6677
6824
  });
6678
6825
  asr.secretKey = await prompter.askSecret({
6679
6826
  label: "ASR secretKey\uFF08\u817E\u8BAF\u4E91\uFF09",
6680
- existingValue: toTrimmedString(existingAsr.secretKey),
6827
+ existingValue: toTrimmedString2(existingAsr.secretKey),
6681
6828
  required: true
6682
6829
  });
6683
6830
  }
@@ -6691,12 +6838,12 @@ async function configureQQBot(prompter, cfg) {
6691
6838
  const existingAsr = isRecord(existing.asr) ? existing.asr : {};
6692
6839
  const appId = await prompter.askText({
6693
6840
  label: "QQBot appId",
6694
- defaultValue: toTrimmedString(existing.appId),
6841
+ defaultValue: toTrimmedString2(existing.appId),
6695
6842
  required: true
6696
6843
  });
6697
6844
  const clientSecret = await prompter.askSecret({
6698
6845
  label: "QQBot clientSecret",
6699
- existingValue: toTrimmedString(existing.clientSecret),
6846
+ existingValue: toTrimmedString2(existing.clientSecret),
6700
6847
  required: true
6701
6848
  });
6702
6849
  const asrEnabled = await prompter.askConfirm(
@@ -6710,17 +6857,17 @@ async function configureQQBot(prompter, cfg) {
6710
6857
  Ve("ASR \u5F00\u901A\u65B9\u5F0F\u8BE6\u60C5\u8BF7\u67E5\u770B\u914D\u7F6E\u6587\u6863\u3002", "\u63D0\u793A");
6711
6858
  asr.appId = await prompter.askText({
6712
6859
  label: "ASR appId\uFF08\u817E\u8BAF\u4E91\uFF09",
6713
- defaultValue: toTrimmedString(existingAsr.appId),
6860
+ defaultValue: toTrimmedString2(existingAsr.appId),
6714
6861
  required: true
6715
6862
  });
6716
6863
  asr.secretId = await prompter.askSecret({
6717
6864
  label: "ASR secretId\uFF08\u817E\u8BAF\u4E91\uFF09",
6718
- existingValue: toTrimmedString(existingAsr.secretId),
6865
+ existingValue: toTrimmedString2(existingAsr.secretId),
6719
6866
  required: true
6720
6867
  });
6721
6868
  asr.secretKey = await prompter.askSecret({
6722
6869
  label: "ASR secretKey\uFF08\u817E\u8BAF\u4E91\uFF09",
6723
- existingValue: toTrimmedString(existingAsr.secretKey),
6870
+ existingValue: toTrimmedString2(existingAsr.secretKey),
6724
6871
  required: true
6725
6872
  });
6726
6873
  }
@@ -7628,11 +7775,14 @@ var dingtalkOutbound = {
7628
7775
  * 发送文本消息
7629
7776
  */
7630
7777
  sendText: async (params) => {
7631
- const { cfg, to, text } = params;
7632
- const dingtalkCfg = cfg.channels?.dingtalk;
7633
- if (!dingtalkCfg) {
7778
+ const { cfg, to, text, accountId } = params;
7779
+ if (!cfg.channels?.dingtalk) {
7634
7780
  throw new Error("DingTalk channel not configured");
7635
7781
  }
7782
+ const dingtalkCfg = mergeDingtalkAccountConfig(
7783
+ cfg,
7784
+ resolveDingtalkAccountId(cfg, accountId)
7785
+ );
7636
7786
  const { targetId, chatType } = parseTarget(to);
7637
7787
  const result = await sendMessageDingtalk({
7638
7788
  cfg: dingtalkCfg,
@@ -7651,11 +7801,14 @@ var dingtalkOutbound = {
7651
7801
  * 发送媒体消息(含回退逻辑)
7652
7802
  */
7653
7803
  sendMedia: async (params) => {
7654
- const { cfg, to, text, mediaUrl } = params;
7655
- const dingtalkCfg = cfg.channels?.dingtalk;
7656
- if (!dingtalkCfg) {
7804
+ const { cfg, to, text, mediaUrl, accountId } = params;
7805
+ if (!cfg.channels?.dingtalk) {
7657
7806
  throw new Error("DingTalk channel not configured");
7658
7807
  }
7808
+ const dingtalkCfg = mergeDingtalkAccountConfig(
7809
+ cfg,
7810
+ resolveDingtalkAccountId(cfg, accountId)
7811
+ );
7659
7812
  const { targetId, chatType } = parseTarget(to);
7660
7813
  if (text?.trim()) {
7661
7814
  await sendMessageDingtalk({
@@ -8418,6 +8571,7 @@ async function handleDingtalkMessage(params) {
8418
8571
  const {
8419
8572
  cfg,
8420
8573
  raw,
8574
+ accountId = DEFAULT_ACCOUNT_ID,
8421
8575
  enableAICard = false
8422
8576
  } = params;
8423
8577
  const logger = createLogger("dingtalk", {
@@ -8435,8 +8589,9 @@ async function handleDingtalkMessage(params) {
8435
8589
  mentionedBot: ctx.mentionedBot
8436
8590
  })}`
8437
8591
  );
8438
- const dingtalkCfg = cfg?.channels;
8439
- const channelCfg = dingtalkCfg?.dingtalk;
8592
+ const pluginCfg = cfg ?? {};
8593
+ const hasChannelConfig = Boolean(pluginCfg.channels?.dingtalk);
8594
+ const channelCfg = hasChannelConfig ? mergeDingtalkAccountConfig(pluginCfg, accountId) : void 0;
8440
8595
  const inboundMediaDir = resolveInboundMediaDir(channelCfg);
8441
8596
  const inboundMediaKeepDays = resolveInboundMediaKeepDays(channelCfg);
8442
8597
  const inboundMediaTempDir = resolveInboundMediaTempDir();
@@ -8516,6 +8671,7 @@ async function handleDingtalkMessage(params) {
8516
8671
  const route = resolveAgentRoute({
8517
8672
  cfg,
8518
8673
  channel: "dingtalk",
8674
+ accountId,
8519
8675
  peer: {
8520
8676
  kind: isGroup ? "group" : "dm",
8521
8677
  id: isGroup ? ctx.conversationId : ctx.senderId
@@ -8607,7 +8763,11 @@ async function handleDingtalkMessage(params) {
8607
8763
  downloadedRichTextImages = [];
8608
8764
  }
8609
8765
  }
8610
- const inboundCtx = buildInboundContext(ctx, route?.sessionKey, route?.accountId);
8766
+ const inboundCtx = buildInboundContext(
8767
+ ctx,
8768
+ route?.sessionKey,
8769
+ route?.accountId ?? accountId
8770
+ );
8611
8771
  if (audioRecognition) {
8612
8772
  inboundCtx.Transcript = audioRecognition;
8613
8773
  }
@@ -8979,15 +9139,15 @@ function trimMessageDedupeCache(now) {
8979
9139
  processedMessages.delete(oldest);
8980
9140
  }
8981
9141
  }
8982
- function isDuplicateMessage(streamMessageId, now) {
8983
- const previous = processedMessages.get(streamMessageId);
9142
+ function isDuplicateMessage(messageKey, now) {
9143
+ const previous = processedMessages.get(messageKey);
8984
9144
  if (typeof previous === "number" && now - previous < MESSAGE_DEDUP_TTL_MS) {
8985
9145
  return true;
8986
9146
  }
8987
9147
  if (typeof previous === "number") {
8988
- processedMessages.delete(streamMessageId);
9148
+ processedMessages.delete(messageKey);
8989
9149
  }
8990
- processedMessages.set(streamMessageId, now);
9150
+ processedMessages.set(messageKey, now);
8991
9151
  trimMessageDedupeCache(now);
8992
9152
  return false;
8993
9153
  }
@@ -9035,6 +9195,7 @@ function processDingtalkInbound(params) {
9035
9195
  onParseError
9036
9196
  } = params;
9037
9197
  const streamMessageId = payload?.headers?.messageId;
9198
+ const dedupeKey = streamMessageId ? `${accountId}:${streamMessageId}` : void 0;
9038
9199
  if (streamMessageId) {
9039
9200
  try {
9040
9201
  client.socketCallBackResponse(streamMessageId, { success: true });
@@ -9043,7 +9204,7 @@ function processDingtalkInbound(params) {
9043
9204
  logger.error(`failed to ACK message ${streamMessageId}: ${String(err)}`);
9044
9205
  }
9045
9206
  }
9046
- if (streamMessageId && isDuplicateMessage(streamMessageId, Date.now())) {
9207
+ if (dedupeKey && isDuplicateMessage(dedupeKey, Date.now())) {
9047
9208
  onDedupeHit?.(streamMessageId);
9048
9209
  logger.debug(`duplicate message ignored: ${streamMessageId}`);
9049
9210
  return;
@@ -9073,7 +9234,7 @@ function processDingtalkInbound(params) {
9073
9234
  }
9074
9235
  }
9075
9236
  function registerDingtalkBotHandler(params) {
9076
- const dingtalkCfg = params.config?.channels?.dingtalk;
9237
+ const dingtalkCfg = params.config ? mergeDingtalkAccountConfig(params.config, params.accountId) : void 0;
9077
9238
  params.client.registerCallbackListener(TOPIC_ROBOT, (payload) => {
9078
9239
  processDingtalkInbound({
9079
9240
  payload,
@@ -9114,8 +9275,19 @@ var DISCONNECT_GRACE_MS = 15e3;
9114
9275
  var RECONNECT_BASE_DELAY_MS = 1e3;
9115
9276
  var RECONNECT_MAX_DELAY_MS = 6e4;
9116
9277
  var RECONNECT_JITTER_RATIO = 0.2;
9117
- var currentAccountId = null;
9118
- var currentPromise = null;
9278
+ var activeConnections = /* @__PURE__ */ new Map();
9279
+ function getOrCreateConnection(accountId) {
9280
+ let conn = activeConnections.get(accountId);
9281
+ if (!conn) {
9282
+ conn = {
9283
+ client: null,
9284
+ promise: null,
9285
+ stop: null
9286
+ };
9287
+ activeConnections.set(accountId, conn);
9288
+ }
9289
+ return conn;
9290
+ }
9119
9291
  function toRecord(value) {
9120
9292
  if (!value || typeof value !== "object" || Array.isArray(value)) {
9121
9293
  return {};
@@ -9350,7 +9522,7 @@ async function runGatewaySession(params) {
9350
9522
  }
9351
9523
  }
9352
9524
  async function runGatewayLoop(params) {
9353
- const { config, dingtalkCfg, accountId, logger, signal } = params;
9525
+ const { config, dingtalkCfg, accountId, logger, signal, conn, setStatus } = params;
9354
9526
  const metrics = createGatewayMetrics();
9355
9527
  const stateRef = { state: "idle" };
9356
9528
  let reconnectAttempt = 0;
@@ -9367,6 +9539,7 @@ async function runGatewayLoop(params) {
9367
9539
  logger.error(`[gateway] fatal client init error: ${String(err)}`);
9368
9540
  throw err;
9369
9541
  }
9542
+ conn.client = client;
9370
9543
  try {
9371
9544
  sessionResult = await runGatewaySession({
9372
9545
  client,
@@ -9382,6 +9555,9 @@ async function runGatewayLoop(params) {
9382
9555
  sessionResult = { kind: "reconnect", reason: "session_error" };
9383
9556
  } finally {
9384
9557
  safeDisconnect(client, logger);
9558
+ if (conn.client === client) {
9559
+ conn.client = null;
9560
+ }
9385
9561
  }
9386
9562
  if (sessionResult.kind === "stopped" || signal.aborted) {
9387
9563
  break;
@@ -9400,33 +9576,46 @@ async function runGatewayLoop(params) {
9400
9576
  logger.warn(
9401
9577
  `[gateway] reconnect scheduled in ${delayMs}ms (attempt=${reconnectAttempt}, reason=${sessionResult.reason})`
9402
9578
  );
9579
+ setStatus?.({
9580
+ accountId,
9581
+ state: "reconnecting",
9582
+ reconnectAttempt,
9583
+ reconnectReason: sessionResult.reason,
9584
+ lastReconnectAt: metrics.lastReconnectAt
9585
+ });
9403
9586
  const keepRunning = await sleepWithAbort(delayMs, signal);
9404
9587
  if (!keepRunning) {
9405
9588
  break;
9406
9589
  }
9407
9590
  }
9408
9591
  transitionState({ ref: stateRef, next: "stopped", logger, reason: "abort/stop" });
9592
+ setStatus?.({
9593
+ accountId,
9594
+ state: "stopped",
9595
+ reconnectCountTotal: metrics.reconnectCountTotal,
9596
+ ackFailCount: metrics.ackFailCount,
9597
+ dedupeHitCount: metrics.dedupeHitCount,
9598
+ parseErrorCount: metrics.parseErrorCount
9599
+ });
9409
9600
  logger.info(
9410
9601
  `[gateway] stopped; reconnects=${metrics.reconnectCountTotal} ackFail=${metrics.ackFailCount} dedupeHit=${metrics.dedupeHitCount} parseErr=${metrics.parseErrorCount}`
9411
9602
  );
9412
9603
  }
9413
9604
  async function monitorDingtalkProvider(opts = {}) {
9414
- const { config, runtime: runtime2, abortSignal, accountId = "default" } = opts;
9605
+ const { config, runtime: runtime2, abortSignal, accountId = DEFAULT_ACCOUNT_ID, setStatus } = opts;
9415
9606
  const logger = createLogger2("dingtalk", {
9416
9607
  log: runtime2?.log,
9417
9608
  error: runtime2?.error
9418
9609
  });
9419
- if (currentPromise) {
9420
- if (currentAccountId && currentAccountId !== accountId) {
9421
- throw new Error(`DingTalk already running for account ${currentAccountId}`);
9422
- }
9610
+ const conn = getOrCreateConnection(accountId);
9611
+ if (conn.promise) {
9423
9612
  logger.debug(`existing gateway for account ${accountId} is active, reusing promise`);
9424
- return currentPromise;
9613
+ return conn.promise;
9425
9614
  }
9426
- const dingtalkCfg = config?.channels?.dingtalk;
9427
- if (!dingtalkCfg) {
9428
- throw new Error("DingTalk configuration not found");
9615
+ if (!config?.channels?.dingtalk) {
9616
+ throw new Error(`DingTalk configuration not found for account ${accountId}`);
9429
9617
  }
9618
+ const dingtalkCfg = mergeDingtalkAccountConfig(config, accountId);
9430
9619
  await ensureGatewayHttpEnabled({ dingtalkCfg, logger });
9431
9620
  const stopController = new AbortController();
9432
9621
  const stopSignal = stopController.signal;
@@ -9438,23 +9627,43 @@ async function monitorDingtalkProvider(opts = {}) {
9438
9627
  } else {
9439
9628
  abortSignal?.addEventListener("abort", onAbort, { once: true });
9440
9629
  }
9441
- currentAccountId = accountId;
9630
+ conn.stop = () => {
9631
+ logger.info("stop requested, stopping Stream gateway");
9632
+ stopController.abort();
9633
+ };
9442
9634
  const runPromise = runGatewayLoop({
9443
9635
  config,
9444
9636
  dingtalkCfg,
9445
9637
  accountId,
9446
9638
  logger,
9447
- signal: stopSignal
9639
+ signal: stopSignal,
9640
+ conn,
9641
+ setStatus
9448
9642
  }).finally(() => {
9449
9643
  abortSignal?.removeEventListener("abort", onAbort);
9450
- if (currentPromise === runPromise) {
9451
- currentAccountId = null;
9452
- currentPromise = null;
9644
+ if (conn.promise === runPromise) {
9645
+ conn.client = null;
9646
+ conn.stop = null;
9647
+ conn.promise = null;
9648
+ activeConnections.delete(accountId);
9453
9649
  }
9454
9650
  });
9455
- currentPromise = runPromise;
9651
+ conn.promise = runPromise;
9456
9652
  return runPromise;
9457
9653
  }
9654
+ function stopDingtalkMonitorForAccount(accountId = DEFAULT_ACCOUNT_ID) {
9655
+ const conn = activeConnections.get(accountId);
9656
+ if (!conn) return;
9657
+ if (conn.stop) {
9658
+ conn.stop();
9659
+ return;
9660
+ }
9661
+ if (conn.client) {
9662
+ const logger = createLogger2("dingtalk");
9663
+ safeDisconnect(conn.client, logger);
9664
+ }
9665
+ activeConnections.delete(accountId);
9666
+ }
9458
9667
 
9459
9668
  // src/onboarding.ts
9460
9669
  function setDingtalkDmPolicy(cfg, dmPolicy2) {
@@ -9560,13 +9769,19 @@ var dingtalkOnboardingAdapter = {
9560
9769
  * 获取渠道配置状态
9561
9770
  */
9562
9771
  getStatus: async (params) => {
9563
- const dingtalkCfg = params.cfg.channels?.dingtalk;
9564
- const configured = isConfigured(dingtalkCfg);
9772
+ const accountIds = listDingtalkAccountIds(params.cfg);
9773
+ const configuredAccountId = accountIds.find(
9774
+ (accountId) => isConfigured(mergeDingtalkAccountConfig(params.cfg, accountId))
9775
+ );
9776
+ const configured = Boolean(configuredAccountId);
9777
+ const defaultAccountId = resolveDefaultDingtalkAccountId(params.cfg);
9565
9778
  const statusLines = [];
9566
9779
  if (!configured) {
9567
9780
  statusLines.push("\u9489\u9489: \u9700\u8981\u914D\u7F6E\u5E94\u7528\u51ED\u8BC1");
9568
9781
  } else {
9569
- statusLines.push("\u9489\u9489: \u5DF2\u914D\u7F6E");
9782
+ statusLines.push(
9783
+ configuredAccountId && configuredAccountId !== DEFAULT_ACCOUNT_ID ? `\u9489\u9489: \u5DF2\u914D\u7F6E (${configuredAccountId})` : `\u9489\u9489: \u5DF2\u914D\u7F6E${defaultAccountId !== DEFAULT_ACCOUNT_ID ? ` (default=${defaultAccountId})` : ""}`
9784
+ );
9570
9785
  }
9571
9786
  return {
9572
9787
  channel: "dingtalk",
@@ -9580,7 +9795,8 @@ var dingtalkOnboardingAdapter = {
9580
9795
  * 交互式配置向导
9581
9796
  */
9582
9797
  configure: async (params) => {
9583
- const dingtalkCfg = params.cfg.channels?.dingtalk;
9798
+ const defaultAccountId = resolveDefaultDingtalkAccountId(params.cfg);
9799
+ const dingtalkCfg = mergeDingtalkAccountConfig(params.cfg, defaultAccountId);
9584
9800
  const resolved = resolveDingtalkCredentials(dingtalkCfg);
9585
9801
  const hasConfigCreds = Boolean(
9586
9802
  dingtalkCfg?.clientId?.trim() && dingtalkCfg?.clientSecret?.trim()
@@ -9710,7 +9926,6 @@ var dingtalkOnboardingAdapter = {
9710
9926
  };
9711
9927
 
9712
9928
  // src/channel.ts
9713
- var DEFAULT_ACCOUNT_ID = "default";
9714
9929
  var meta = {
9715
9930
  id: "dingtalk",
9716
9931
  label: "DingTalk",
@@ -9722,19 +9937,36 @@ var meta = {
9722
9937
  order: 71
9723
9938
  };
9724
9939
  function resolveDingtalkAccount(params) {
9725
- const { cfg, accountId = DEFAULT_ACCOUNT_ID } = params;
9726
- const dingtalkCfg = cfg.channels?.dingtalk;
9727
- const parsed = dingtalkCfg ? DingtalkConfigSchema.safeParse(dingtalkCfg) : null;
9728
- const config = parsed?.success ? parsed.data : void 0;
9729
- const credentials = resolveDingtalkCredentials(config);
9940
+ const { cfg } = params;
9941
+ const accountId = resolveDingtalkAccountId(cfg, params.accountId);
9942
+ const merged = mergeDingtalkAccountConfig(cfg, accountId);
9943
+ const baseEnabled = cfg.channels?.dingtalk?.enabled !== false;
9944
+ const enabled = baseEnabled && merged.enabled !== false;
9945
+ const credentials = resolveDingtalkCredentials(merged);
9730
9946
  const configured = Boolean(credentials);
9731
9947
  return {
9732
9948
  accountId,
9733
- enabled: config?.enabled ?? true,
9949
+ enabled,
9734
9950
  configured,
9735
9951
  clientId: credentials?.clientId
9736
9952
  };
9737
9953
  }
9954
+ function canStoreDefaultAccountInAccounts(cfg) {
9955
+ return Boolean(cfg.channels?.dingtalk?.accounts?.[DEFAULT_ACCOUNT_ID]);
9956
+ }
9957
+ function resolveRuntimeCandidate(params) {
9958
+ const runtimeRecord = params.runtime && typeof params.runtime === "object" ? params.runtime : void 0;
9959
+ const runtimeChannel = runtimeRecord?.channel && typeof runtimeRecord.channel === "object" ? runtimeRecord.channel : void 0;
9960
+ const channelRuntime = params.channelRuntime && typeof params.channelRuntime === "object" ? params.channelRuntime : void 0;
9961
+ const resolvedChannel = channelRuntime ?? (runtimeChannel?.routing || runtimeChannel?.reply || runtimeChannel?.session || runtimeChannel?.text ? runtimeChannel : void 0);
9962
+ if (!resolvedChannel) {
9963
+ return runtimeRecord;
9964
+ }
9965
+ return {
9966
+ ...runtimeRecord ?? {},
9967
+ channel: resolvedChannel
9968
+ };
9969
+ }
9738
9970
  var dingtalkPlugin = {
9739
9971
  id: "dingtalk",
9740
9972
  /**
@@ -9768,8 +10000,11 @@ var dingtalkPlugin = {
9768
10000
  additionalProperties: false,
9769
10001
  properties: {
9770
10002
  enabled: { type: "boolean" },
10003
+ name: { type: "string" },
10004
+ defaultAccount: { type: "string" },
9771
10005
  clientId: { type: "string" },
9772
10006
  clientSecret: { type: "string" },
10007
+ connectionMode: { type: "string", enum: ["stream", "webhook"] },
9773
10008
  dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
9774
10009
  groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
9775
10010
  requireMention: { type: "boolean" },
@@ -9789,6 +10024,40 @@ var dingtalkPlugin = {
9789
10024
  dir: { type: "string" },
9790
10025
  keepDays: { type: "number", minimum: 0 }
9791
10026
  }
10027
+ },
10028
+ accounts: {
10029
+ type: "object",
10030
+ additionalProperties: {
10031
+ type: "object",
10032
+ additionalProperties: false,
10033
+ properties: {
10034
+ name: { type: "string" },
10035
+ enabled: { type: "boolean" },
10036
+ clientId: { type: "string" },
10037
+ clientSecret: { type: "string" },
10038
+ connectionMode: { type: "string", enum: ["stream", "webhook"] },
10039
+ dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
10040
+ groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
10041
+ requireMention: { type: "boolean" },
10042
+ allowFrom: { type: "array", items: { type: "string" } },
10043
+ groupAllowFrom: { type: "array", items: { type: "string" } },
10044
+ historyLimit: { type: "integer", minimum: 0 },
10045
+ textChunkLimit: { type: "integer", minimum: 1 },
10046
+ longTaskNoticeDelayMs: { type: "integer", minimum: 0 },
10047
+ enableAICard: { type: "boolean" },
10048
+ gatewayToken: { type: "string" },
10049
+ gatewayPassword: { type: "string" },
10050
+ maxFileSizeMB: { type: "number", minimum: 0 },
10051
+ inboundMedia: {
10052
+ type: "object",
10053
+ additionalProperties: false,
10054
+ properties: {
10055
+ dir: { type: "string" },
10056
+ keepDays: { type: "number", minimum: 0 }
10057
+ }
10058
+ }
10059
+ }
10060
+ }
9792
10061
  }
9793
10062
  }
9794
10063
  }
@@ -9806,7 +10075,7 @@ var dingtalkPlugin = {
9806
10075
  * 列出所有账户 ID
9807
10076
  * Requirements: 2.1
9808
10077
  */
9809
- listAccountIds: (_cfg) => [DEFAULT_ACCOUNT_ID],
10078
+ listAccountIds: (cfg) => listDingtalkAccountIds(cfg),
9810
10079
  /**
9811
10080
  * 解析账户配置
9812
10081
  * Requirements: 2.2
@@ -9815,19 +10084,38 @@ var dingtalkPlugin = {
9815
10084
  /**
9816
10085
  * 获取默认账户 ID
9817
10086
  */
9818
- defaultAccountId: () => DEFAULT_ACCOUNT_ID,
10087
+ defaultAccountId: (cfg) => resolveDefaultDingtalkAccountId(cfg),
9819
10088
  /**
9820
10089
  * 设置账户启用状态
9821
10090
  */
9822
10091
  setAccountEnabled: (params) => {
9823
- const existingConfig = params.cfg.channels?.dingtalk ?? {};
10092
+ const accountId = resolveDingtalkAccountId(params.cfg, params.accountId);
10093
+ const seededCfg = moveDingtalkSingleAccountConfigToDefaultAccount(params.cfg);
10094
+ const existing = seededCfg.channels?.dingtalk ?? {};
10095
+ if (accountId === DEFAULT_ACCOUNT_ID && !canStoreDefaultAccountInAccounts(seededCfg)) {
10096
+ return {
10097
+ ...seededCfg,
10098
+ channels: {
10099
+ ...seededCfg.channels,
10100
+ dingtalk: {
10101
+ ...existing,
10102
+ enabled: params.enabled
10103
+ }
10104
+ }
10105
+ };
10106
+ }
10107
+ const accounts = existing.accounts ?? {};
10108
+ const account = accounts[accountId] ?? {};
9824
10109
  return {
9825
- ...params.cfg,
10110
+ ...seededCfg,
9826
10111
  channels: {
9827
- ...params.cfg.channels,
10112
+ ...seededCfg.channels,
9828
10113
  dingtalk: {
9829
- ...existingConfig,
9830
- enabled: params.enabled
10114
+ ...existing,
10115
+ accounts: {
10116
+ ...accounts,
10117
+ [accountId]: { ...account, enabled: params.enabled }
10118
+ }
9831
10119
  }
9832
10120
  }
9833
10121
  };
@@ -9836,21 +10124,68 @@ var dingtalkPlugin = {
9836
10124
  * 删除账户配置
9837
10125
  */
9838
10126
  deleteAccount: (params) => {
9839
- const next = { ...params.cfg };
9840
- const nextChannels = { ...params.cfg.channels };
9841
- delete nextChannels.dingtalk;
9842
- if (Object.keys(nextChannels).length > 0) {
9843
- next.channels = nextChannels;
9844
- } else {
9845
- delete next.channels;
10127
+ const accountId = resolveDingtalkAccountId(params.cfg, params.accountId);
10128
+ const seededCfg = moveDingtalkSingleAccountConfigToDefaultAccount(params.cfg);
10129
+ const existing = seededCfg.channels?.dingtalk;
10130
+ if (!existing) return seededCfg;
10131
+ const accounts = existing.accounts ?? {};
10132
+ if (!accounts[accountId]) {
10133
+ if (accountId === DEFAULT_ACCOUNT_ID && Object.keys(accounts).length === 0 && !canStoreDefaultAccountInAccounts(seededCfg)) {
10134
+ const next = { ...seededCfg };
10135
+ const nextChannels = { ...seededCfg.channels };
10136
+ delete nextChannels.dingtalk;
10137
+ if (Object.keys(nextChannels).length > 0) {
10138
+ next.channels = nextChannels;
10139
+ } else {
10140
+ delete next.channels;
10141
+ }
10142
+ return next;
10143
+ }
10144
+ return seededCfg;
10145
+ }
10146
+ const { [accountId]: _removed, ...remainingAccounts } = accounts;
10147
+ const remainingIds = Object.keys(remainingAccounts).sort((a, b) => a.localeCompare(b));
10148
+ const preferred = existing.defaultAccount?.trim();
10149
+ let nextDefaultAccount = preferred;
10150
+ if (preferred && !remainingAccounts[preferred]) {
10151
+ nextDefaultAccount = remainingIds.includes(DEFAULT_ACCOUNT_ID) ? DEFAULT_ACCOUNT_ID : remainingIds[0] ?? "";
10152
+ }
10153
+ const nextChannel = {
10154
+ ...existing,
10155
+ accounts: remainingIds.length > 0 ? remainingAccounts : void 0,
10156
+ defaultAccount: nextDefaultAccount || void 0
10157
+ };
10158
+ const hasNonTrivialRootConfig = Object.entries(nextChannel).some(
10159
+ ([key, value]) => key !== "enabled" && key !== "accounts" && key !== "defaultAccount" && value !== void 0
10160
+ );
10161
+ if (remainingIds.length === 0 && !hasNonTrivialRootConfig) {
10162
+ const next = { ...seededCfg };
10163
+ const nextChannels = { ...seededCfg.channels };
10164
+ delete nextChannels.dingtalk;
10165
+ if (Object.keys(nextChannels).length > 0) {
10166
+ next.channels = nextChannels;
10167
+ } else {
10168
+ delete next.channels;
10169
+ }
10170
+ return next;
9846
10171
  }
9847
- return next;
10172
+ return {
10173
+ ...seededCfg,
10174
+ channels: {
10175
+ ...seededCfg.channels,
10176
+ dingtalk: nextChannel
10177
+ }
10178
+ };
9848
10179
  },
9849
10180
  /**
9850
10181
  * 检查账户是否已配置
9851
10182
  * Requirements: 2.3
9852
10183
  */
9853
- isConfigured: (_account, cfg) => isConfigured(cfg.channels?.dingtalk),
10184
+ isConfigured: (_account, cfg, accountId) => {
10185
+ const id = accountId ?? _account.accountId;
10186
+ const merged = mergeDingtalkAccountConfig(cfg, id);
10187
+ return Boolean(merged.clientId && merged.clientSecret);
10188
+ },
9854
10189
  /**
9855
10190
  * 描述账户信息
9856
10191
  */
@@ -9862,7 +10197,11 @@ var dingtalkPlugin = {
9862
10197
  /**
9863
10198
  * 解析白名单
9864
10199
  */
9865
- resolveAllowFrom: (params) => params.cfg.channels?.dingtalk?.allowFrom ?? [],
10200
+ resolveAllowFrom: (params) => {
10201
+ const accountId = resolveDingtalkAccountId(params.cfg, params.accountId);
10202
+ const merged = mergeDingtalkAccountConfig(params.cfg, accountId);
10203
+ return merged.allowFrom ?? [];
10204
+ },
9866
10205
  /**
9867
10206
  * 格式化白名单条目
9868
10207
  */
@@ -9885,16 +10224,40 @@ var dingtalkPlugin = {
9885
10224
  * 设置向导适配器
9886
10225
  */
9887
10226
  setup: {
9888
- resolveAccountId: () => DEFAULT_ACCOUNT_ID,
10227
+ resolveAccountId: (params) => resolveDingtalkAccountId(params.cfg, params.accountId),
9889
10228
  applyAccountConfig: (params) => {
9890
- const existingConfig = params.cfg.channels?.dingtalk ?? {};
10229
+ const accountId = resolveDingtalkAccountId(params.cfg, params.accountId);
10230
+ const seededCfg = moveDingtalkSingleAccountConfigToDefaultAccount(params.cfg);
10231
+ const existing = seededCfg.channels?.dingtalk ?? {};
10232
+ if (accountId === DEFAULT_ACCOUNT_ID && !canStoreDefaultAccountInAccounts(seededCfg)) {
10233
+ return {
10234
+ ...seededCfg,
10235
+ channels: {
10236
+ ...seededCfg.channels,
10237
+ dingtalk: {
10238
+ ...existing,
10239
+ ...params.config,
10240
+ enabled: true
10241
+ }
10242
+ }
10243
+ };
10244
+ }
10245
+ const accounts = existing.accounts ?? {};
9891
10246
  return {
9892
- ...params.cfg,
10247
+ ...seededCfg,
9893
10248
  channels: {
9894
- ...params.cfg.channels,
10249
+ ...seededCfg.channels,
9895
10250
  dingtalk: {
9896
- ...existingConfig,
9897
- enabled: true
10251
+ ...existing,
10252
+ enabled: true,
10253
+ accounts: {
10254
+ ...accounts,
10255
+ [accountId]: {
10256
+ ...accounts[accountId],
10257
+ ...params.config,
10258
+ enabled: true
10259
+ }
10260
+ }
9898
10261
  }
9899
10262
  }
9900
10263
  };
@@ -9921,11 +10284,13 @@ var dingtalkPlugin = {
9921
10284
  startAccount: async (ctx) => {
9922
10285
  ctx.setStatus?.({ accountId: ctx.accountId });
9923
10286
  ctx.log?.info(`[dingtalk] starting provider for account ${ctx.accountId}`);
9924
- if (ctx.runtime) {
9925
- const candidate = ctx.runtime;
9926
- if (candidate.channel?.routing?.resolveAgentRoute && candidate.channel?.reply?.dispatchReplyFromConfig) {
9927
- setDingtalkRuntime(ctx.runtime);
9928
- }
10287
+ const runtimeCandidate = resolveRuntimeCandidate({
10288
+ runtime: ctx.runtime,
10289
+ channelRuntime: ctx.channelRuntime
10290
+ });
10291
+ const candidate = runtimeCandidate;
10292
+ if (candidate?.channel?.routing?.resolveAgentRoute && candidate.channel?.reply?.dispatchReplyFromConfig) {
10293
+ setDingtalkRuntime(runtimeCandidate);
9929
10294
  }
9930
10295
  return monitorDingtalkProvider({
9931
10296
  config: ctx.cfg,
@@ -9934,9 +10299,14 @@ var dingtalkPlugin = {
9934
10299
  error: ctx.log?.error ?? console.error
9935
10300
  },
9936
10301
  abortSignal: ctx.abortSignal,
9937
- accountId: ctx.accountId
10302
+ accountId: ctx.accountId,
10303
+ setStatus: ctx.setStatus
9938
10304
  });
9939
- }
10305
+ },
10306
+ stopAccount: async (ctx) => {
10307
+ stopDingtalkMonitorForAccount(ctx.accountId);
10308
+ },
10309
+ getStatus: () => ({ connected: true })
9940
10310
  }
9941
10311
  };
9942
10312
 
@@ -9950,6 +10320,8 @@ var plugin = {
9950
10320
  additionalProperties: false,
9951
10321
  properties: {
9952
10322
  enabled: { type: "boolean" },
10323
+ name: { type: "string" },
10324
+ defaultAccount: { type: "string" },
9953
10325
  clientId: { type: "string" },
9954
10326
  clientSecret: { type: "string" },
9955
10327
  dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
@@ -9959,7 +10331,54 @@ var plugin = {
9959
10331
  groupAllowFrom: { type: "array", items: { type: "string" } },
9960
10332
  historyLimit: { type: "integer", minimum: 0 },
9961
10333
  textChunkLimit: { type: "integer", minimum: 1 },
9962
- longTaskNoticeDelayMs: { type: "integer", minimum: 0 }
10334
+ longTaskNoticeDelayMs: { type: "integer", minimum: 0 },
10335
+ connectionMode: { type: "string", enum: ["stream", "webhook"] },
10336
+ enableAICard: { type: "boolean" },
10337
+ gatewayToken: { type: "string" },
10338
+ gatewayPassword: { type: "string" },
10339
+ maxFileSizeMB: { type: "number", minimum: 1 },
10340
+ inboundMedia: {
10341
+ type: "object",
10342
+ additionalProperties: false,
10343
+ properties: {
10344
+ dir: { type: "string" },
10345
+ keepDays: { type: "number", minimum: 0 }
10346
+ }
10347
+ },
10348
+ accounts: {
10349
+ type: "object",
10350
+ additionalProperties: {
10351
+ type: "object",
10352
+ additionalProperties: false,
10353
+ properties: {
10354
+ name: { type: "string" },
10355
+ enabled: { type: "boolean" },
10356
+ clientId: { type: "string" },
10357
+ clientSecret: { type: "string" },
10358
+ dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
10359
+ groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
10360
+ requireMention: { type: "boolean" },
10361
+ allowFrom: { type: "array", items: { type: "string" } },
10362
+ groupAllowFrom: { type: "array", items: { type: "string" } },
10363
+ historyLimit: { type: "integer", minimum: 0 },
10364
+ textChunkLimit: { type: "integer", minimum: 1 },
10365
+ longTaskNoticeDelayMs: { type: "integer", minimum: 0 },
10366
+ connectionMode: { type: "string", enum: ["stream", "webhook"] },
10367
+ enableAICard: { type: "boolean" },
10368
+ gatewayToken: { type: "string" },
10369
+ gatewayPassword: { type: "string" },
10370
+ maxFileSizeMB: { type: "number", minimum: 1 },
10371
+ inboundMedia: {
10372
+ type: "object",
10373
+ additionalProperties: false,
10374
+ properties: {
10375
+ dir: { type: "string" },
10376
+ keepDays: { type: "number", minimum: 0 }
10377
+ }
10378
+ }
10379
+ }
10380
+ }
10381
+ }
9963
10382
  }
9964
10383
  },
9965
10384
  /**