@soimy/dingtalk 3.6.3 → 3.6.4

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
@@ -9584,6 +9584,7 @@ function createCardReplyStrategy(ctx) {
9584
9584
  let sawFinalDelivery = false;
9585
9585
  let latestReasoningSnapshot = "";
9586
9586
  let pendingNonImageMedia = [];
9587
+ const processedMediaUrls = /* @__PURE__ */ new Set();
9587
9588
  const getRenderedTimeline = (options = {}) => {
9588
9589
  const fallbackAnswer = finalTextForFallback || (sawFinalDelivery ? EMPTY_FINAL_REPLY : void 0);
9589
9590
  return controller.getRenderedContent({
@@ -9752,6 +9753,11 @@ function createCardReplyStrategy(ctx) {
9752
9753
  if (candidate.classification !== "local") {
9753
9754
  continue;
9754
9755
  }
9756
+ if (processedMediaUrls.has(candidate.url.trim())) {
9757
+ const placeholder = buildImagePlaceholderText({ alt: candidate.alt, url: candidate.url });
9758
+ nextText = `${nextText.slice(0, candidate.start)}${placeholder}${nextText.slice(candidate.end)}`;
9759
+ continue;
9760
+ }
9755
9761
  let prepared;
9756
9762
  try {
9757
9763
  prepared = await prepareMediaInput(candidate.url, log, config.mediaUrlAllowlist);
@@ -9764,6 +9770,7 @@ function createCardReplyStrategy(ctx) {
9764
9770
  if (!result?.mediaId) {
9765
9771
  continue;
9766
9772
  }
9773
+ processedMediaUrls.add(candidate.url.trim());
9767
9774
  const placeholder = buildImagePlaceholderText({ alt: candidate.alt, url: candidate.url });
9768
9775
  const blockText = candidate.alt.trim() || placeholder.replace(/^见下图/, "").trim() || "\u56FE\u7247";
9769
9776
  successfulReroutes.push({
@@ -9863,6 +9870,10 @@ function createCardReplyStrategy(ctx) {
9863
9870
  );
9864
9871
  if (payload.mediaUrls.length > 0) {
9865
9872
  for (const url of payload.mediaUrls) {
9873
+ const normalizedUrl = url.trim();
9874
+ if (processedMediaUrls.has(normalizedUrl)) {
9875
+ continue;
9876
+ }
9866
9877
  try {
9867
9878
  const prepared = await prepareMediaInput(url, log, config.mediaUrlAllowlist);
9868
9879
  const mediaType = resolveOutboundMediaType({ mediaPath: prepared.path, asVoice: false });
@@ -9877,6 +9888,7 @@ function createCardReplyStrategy(ctx) {
9877
9888
  const result = await uploadMedia2(config, prepared.path, "image", log);
9878
9889
  await prepared.cleanup?.();
9879
9890
  if (result?.mediaId) {
9891
+ processedMediaUrls.add(normalizedUrl);
9880
9892
  await controller.appendImageBlock(result.mediaId);
9881
9893
  }
9882
9894
  } catch (err) {
@@ -9935,6 +9947,10 @@ function createCardReplyStrategy(ctx) {
9935
9947
  }
9936
9948
  if (payload.mediaUrls.length > 0) {
9937
9949
  for (const url of payload.mediaUrls) {
9950
+ const normalizedUrl = url.trim();
9951
+ if (processedMediaUrls.has(normalizedUrl)) {
9952
+ continue;
9953
+ }
9938
9954
  try {
9939
9955
  const prepared = await prepareMediaInput(url, log, config.mediaUrlAllowlist);
9940
9956
  const mediaType = resolveOutboundMediaType({ mediaPath: prepared.path, asVoice: false });
@@ -9947,6 +9963,7 @@ function createCardReplyStrategy(ctx) {
9947
9963
  const result = await uploadMedia2(config, prepared.path, "image", log);
9948
9964
  await prepared.cleanup?.();
9949
9965
  if (result?.mediaId) {
9966
+ processedMediaUrls.add(normalizedUrl);
9950
9967
  await controller.appendImageBlock(result.mediaId);
9951
9968
  }
9952
9969
  } catch (err) {
@@ -10556,46 +10573,61 @@ function buildAgentSessionKey(params) {
10556
10573
  identityLinks: cfg.session?.identityLinks
10557
10574
  }).toLowerCase();
10558
10575
  }
10559
- function sanitizeAgentName(name) {
10560
- return name.replace(/[[\]\r\n]/g, "").trim();
10561
- }
10562
- async function resolveSubAgentRoute(params) {
10563
- const { extractedContent, cfg, isGroup, dingtalkConfig, sessionWebhook, senderId, log } = params;
10576
+ function resolveMessageTarget(params) {
10577
+ const { extractedContent, cfg, isGroup } = params;
10564
10578
  const atMentions = extractedContent.atMentions || [];
10565
- const atUserDingtalkIds = isGroup ? extractedContent.atUserDingtalkIds : void 0;
10579
+ if (atMentions.length === 0 || !cfg.agents?.list || cfg.agents.list.length === 0) {
10580
+ return { kind: "default" };
10581
+ }
10566
10582
  const textForCommandCheck = extractedContent.text.replace(/^\[引用[^\]]*\]\s*/, "");
10567
- const isLearnCommand = parseLearnCommand(textForCommandCheck).scope !== "unknown";
10568
- const textWithoutMentions = textForCommandCheck.replace(/^(?:@\S+\s+)*/u, "").trim();
10569
- const isSlashCommand = maybeResolveTextAlias(textWithoutMentions, cfg) !== null;
10570
- if (atMentions.length === 0 || !cfg.agents?.list || cfg.agents.list.length === 0 || isLearnCommand || isSlashCommand) {
10571
- return null;
10583
+ if (parseLearnCommand(textForCommandCheck).scope !== "unknown") {
10584
+ return { kind: "default" };
10572
10585
  }
10573
- const { matchedAgents, unmatchedNames, realUserCount, hasInvalidAgentNames } = resolveAtAgents(
10586
+ const atUserDingtalkIds = isGroup ? extractedContent.atUserDingtalkIds : void 0;
10587
+ const { matchedAgents, unmatchedNames, hasInvalidAgentNames } = resolveAtAgents(
10574
10588
  atMentions,
10575
10589
  cfg,
10576
10590
  atUserDingtalkIds
10577
10591
  );
10578
- log?.info?.(
10579
- `[DingTalk] Sub-agent resolve: matched=${matchedAgents.map((a) => a.agentId).join(",")} unmatched=${unmatchedNames.join(",")} realUsers=${realUserCount}`
10580
- );
10581
- if (hasInvalidAgentNames) {
10582
- const fallbackReason = `\u672A\u627E\u5230\u540D\u4E3A"${unmatchedNames.join("\u3001")}"\u7684\u52A9\u624B`;
10583
- try {
10584
- const sendOptions = isGroup ? { atUserId: senderId, log } : { log };
10585
- await sendBySession(dingtalkConfig, sessionWebhook, `\u26A0\uFE0F ${fallbackReason}`, {
10586
- ...sendOptions
10587
- });
10588
- } catch (err) {
10589
- log?.debug?.(`[DingTalk] Failed to send fallback notice: ${getErrorMessage(err)}`);
10592
+ const commandText = textForCommandCheck.replace(/^(?:@\S+\s+)*/u, "").trim();
10593
+ if (maybeResolveTextAlias(commandText, cfg) !== null) {
10594
+ const firstMatch = matchedAgents[0];
10595
+ if (firstMatch) {
10596
+ return { kind: "subagent-command", agent: firstMatch, commandText };
10590
10597
  }
10591
10598
  }
10592
- if (matchedAgents.length === 0) {
10593
- return null;
10599
+ if (matchedAgents.length === 0 && !hasInvalidAgentNames) {
10600
+ return { kind: "default" };
10601
+ }
10602
+ return { kind: "subagent-content", matchedAgents, unmatchedNames, hasInvalidAgentNames };
10603
+ }
10604
+ function sanitizeAgentName(name) {
10605
+ return name.replace(/[[\]\r\n]/g, "").trim();
10606
+ }
10607
+ async function sendUnmatchedAgentNotice(params) {
10608
+ const { unmatchedNames, isGroup, senderId, dingtalkConfig, sessionWebhook, log } = params;
10609
+ const fallbackReason = `\u672A\u627E\u5230\u540D\u4E3A"${unmatchedNames.join("\u3001")}"\u7684\u52A9\u624B`;
10610
+ try {
10611
+ const sendOptions = isGroup ? { atUserId: senderId, log } : { log };
10612
+ await sendBySession(dingtalkConfig, sessionWebhook, `\u26A0\uFE0F ${fallbackReason}`, sendOptions);
10613
+ } catch (err) {
10614
+ log?.debug?.(`[DingTalk] Failed to send fallback notice: ${getErrorMessage(err)}`);
10594
10615
  }
10595
- return { matchedAgents };
10596
10616
  }
10597
10617
  async function dispatchSubAgents(params) {
10598
- const { matchedAgents, cfg, accountId, data, dingtalkConfig, sessionWebhook, extractedContent, handleMessage, downloadMedia: download, log } = params;
10618
+ const {
10619
+ matchedAgents,
10620
+ commandText,
10621
+ cfg,
10622
+ accountId,
10623
+ data,
10624
+ dingtalkConfig,
10625
+ sessionWebhook,
10626
+ extractedContent,
10627
+ handleMessage,
10628
+ downloadMedia: download,
10629
+ log
10630
+ } = params;
10599
10631
  let preDownloadedMedia;
10600
10632
  const robotCode = resolveRobotCode(dingtalkConfig);
10601
10633
  if (robotCode) {
@@ -10630,18 +10662,17 @@ async function dispatchSubAgents(params) {
10630
10662
  dingtalkConfig,
10631
10663
  subAgentOptions: {
10632
10664
  agentId: agentMatch.agentId,
10633
- responsePrefix: `> \u{1F916} **${sanitizeAgentName(agentMatch.matchedName)}**:
10665
+ responsePrefix: commandText ? "" : `> \u{1F916} **${sanitizeAgentName(agentMatch.matchedName)}**:
10634
10666
 
10635
10667
  `,
10636
- matchedName: agentMatch.matchedName
10668
+ matchedName: agentMatch.matchedName,
10669
+ commandText
10637
10670
  },
10638
10671
  preDownloadedMedia
10639
10672
  });
10640
10673
  } catch (error) {
10641
10674
  const message = getErrorMessage(error);
10642
- log?.error?.(
10643
- `[DingTalk] Sub-agent ${agentMatch.agentId} failed: ${message}`
10644
- );
10675
+ log?.error?.(`[DingTalk] Sub-agent ${agentMatch.agentId} failed: ${message}`);
10645
10676
  if (error instanceof HostRoutingHelperUnavailableError && !helperMissingWarningSent) {
10646
10677
  helperMissingWarningSent = true;
10647
10678
  try {
@@ -11361,7 +11392,7 @@ async function handleDingTalkMessage(params) {
11361
11392
  return;
11362
11393
  }
11363
11394
  const rawInboundText = extractedContent.text.trim();
11364
- if (subAgentOptions) {
11395
+ if (subAgentOptions && !subAgentOptions.commandText) {
11365
11396
  const cleanText = extractedContent.text.replace(/^\[引用[^\]]*\]\s*/, "");
11366
11397
  const contextHint = `[\u4F60\u88AB @ \u4E3A"${subAgentOptions.matchedName}"]
11367
11398
 
@@ -11539,36 +11570,62 @@ async function handleDingTalkMessage(params) {
11539
11570
  peerIdOverride,
11540
11571
  config: dingtalkConfig
11541
11572
  });
11542
- const route = subAgentOptions ? {
11543
- agentId: subAgentOptions.agentId,
11544
- sessionKey: buildAgentSessionKey({
11545
- rt,
11546
- cfg,
11547
- accountId,
11573
+ const messageTarget = subAgentOptions ? null : resolveMessageTarget({ extractedContent, cfg, isGroup });
11574
+ let route;
11575
+ if (subAgentOptions) {
11576
+ route = {
11548
11577
  agentId: subAgentOptions.agentId,
11549
- peerKind: sessionPeer.kind,
11550
- peerId: sessionPeer.peerId
11551
- }),
11552
- mainSessionKey: ""
11553
- } : rt.channel.routing.resolveAgentRoute({
11554
- cfg,
11555
- channel: "dingtalk",
11556
- accountId,
11557
- peer: { kind: sessionPeer.kind, id: sessionPeer.peerId }
11558
- });
11559
- if (!subAgentOptions) {
11560
- const subAgentRoute = await resolveSubAgentRoute({
11561
- extractedContent,
11578
+ sessionKey: buildAgentSessionKey({
11579
+ rt,
11580
+ cfg,
11581
+ accountId,
11582
+ agentId: subAgentOptions.agentId,
11583
+ peerKind: sessionPeer.kind,
11584
+ peerId: sessionPeer.peerId
11585
+ }),
11586
+ mainSessionKey: ""
11587
+ };
11588
+ } else {
11589
+ route = rt.channel.routing.resolveAgentRoute({
11562
11590
  cfg,
11563
- isGroup,
11564
- dingtalkConfig,
11565
- sessionWebhook,
11566
- senderId,
11567
- log
11591
+ channel: "dingtalk",
11592
+ accountId,
11593
+ peer: { kind: sessionPeer.kind, id: sessionPeer.peerId }
11568
11594
  });
11569
- if (subAgentRoute) {
11595
+ }
11596
+ if (messageTarget && messageTarget.kind !== "default") {
11597
+ if (messageTarget.kind === "subagent-command") {
11598
+ await dispatchSubAgents({
11599
+ matchedAgents: [messageTarget.agent],
11600
+ commandText: messageTarget.commandText,
11601
+ cfg,
11602
+ accountId,
11603
+ data,
11604
+ dingtalkConfig,
11605
+ sessionWebhook,
11606
+ extractedContent,
11607
+ handleMessage: handleDingTalkMessage,
11608
+ downloadMedia,
11609
+ log
11610
+ });
11611
+ return;
11612
+ }
11613
+ if (messageTarget.hasInvalidAgentNames) {
11614
+ await sendUnmatchedAgentNotice({
11615
+ unmatchedNames: messageTarget.unmatchedNames,
11616
+ isGroup,
11617
+ senderId,
11618
+ dingtalkConfig,
11619
+ sessionWebhook,
11620
+ log
11621
+ });
11622
+ }
11623
+ if (messageTarget.matchedAgents.length > 0) {
11624
+ log?.info?.(
11625
+ `[DingTalk] Sub-agent resolve: matched=${messageTarget.matchedAgents.map((a) => a.agentId).join(",")} unmatched=${messageTarget.unmatchedNames.join(",")}`
11626
+ );
11570
11627
  await dispatchSubAgents({
11571
- ...subAgentRoute,
11628
+ matchedAgents: messageTarget.matchedAgents,
11572
11629
  cfg,
11573
11630
  accountId,
11574
11631
  data,
@@ -11592,7 +11649,7 @@ async function handleDingTalkMessage(params) {
11592
11649
  dingtalkConfig,
11593
11650
  senderId,
11594
11651
  isDirect,
11595
- extractedText: extractedContent.text,
11652
+ extractedText: subAgentOptions?.commandText ?? extractedContent.text,
11596
11653
  messageType: extractedContent.messageType,
11597
11654
  data: {
11598
11655
  conversationId: data.conversationId,
@@ -12215,6 +12272,7 @@ ${extracted.text}`;
12215
12272
  const inboundText = attachmentExtractedText ? `${inboundBody.trimEnd()}
12216
12273
 
12217
12274
  ${attachmentExtractedText}` : inboundBody;
12275
+ const commandBody = subAgentOptions?.commandText ?? inboundText;
12218
12276
  const learningEnabled = isLearningEnabled(dingtalkConfig);
12219
12277
  const learningContextBlock = buildLearningContextBlock({
12220
12278
  enabled: learningEnabled,
@@ -12256,7 +12314,7 @@ ${attachmentExtractedText}` : inboundBody;
12256
12314
  const ctx = rt.channel.reply.finalizeInboundContext({
12257
12315
  Body: body,
12258
12316
  RawBody: inboundText,
12259
- CommandBody: inboundText,
12317
+ CommandBody: commandBody,
12260
12318
  QuotedRef: quotedRef,
12261
12319
  QuotedRefJson: quotedRef ? JSON.stringify(quotedRef) : void 0,
12262
12320
  ReplyToId: quotedRuntimeContext?.replyToId,