@soimy/dingtalk 3.6.2 → 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 +387 -159
- package/dist/index.js.map +3 -3
- package/dist/src/card-draft-controller.d.ts.map +1 -1
- package/dist/src/card-service.d.ts.map +1 -1
- package/dist/src/inbound-handler.d.ts.map +1 -1
- package/dist/src/message-utils.d.ts +10 -0
- package/dist/src/message-utils.d.ts.map +1 -1
- package/dist/src/reply-strategy-card.d.ts.map +1 -1
- package/dist/src/reply-strategy-markdown.d.ts.map +1 -1
- package/dist/src/reply-strategy-types.d.ts +2 -0
- package/dist/src/reply-strategy-types.d.ts.map +1 -1
- package/dist/src/send-service.d.ts.map +1 -1
- package/dist/src/targeting/agent-routing.d.ts +55 -16
- package/dist/src/targeting/agent-routing.d.ts.map +1 -1
- package/dist/src/types.d.ts +7 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/card-callback-service.ts +1 -1
- package/src/card-draft-controller.ts +4 -3
- package/src/card-service.ts +28 -4
- package/src/inbound-handler.ts +82 -33
- package/src/message-utils.ts +103 -7
- package/src/reply-strategy-card.ts +31 -3
- package/src/reply-strategy-markdown.ts +139 -20
- package/src/reply-strategy-types.ts +3 -0
- package/src/send-service.ts +67 -65
- package/src/targeting/agent-routing.ts +151 -85
- package/src/types.ts +7 -0
package/dist/index.js
CHANGED
|
@@ -1814,7 +1814,7 @@ async function updateCardVariables(outTrackId, params, token, config) {
|
|
|
1814
1814
|
{
|
|
1815
1815
|
outTrackId,
|
|
1816
1816
|
cardData: { cardParamMap: stringMap },
|
|
1817
|
-
cardUpdateOptions: { updateCardDataByKey: true
|
|
1817
|
+
cardUpdateOptions: { updateCardDataByKey: true }
|
|
1818
1818
|
},
|
|
1819
1819
|
{
|
|
1820
1820
|
headers: {
|
|
@@ -2614,6 +2614,19 @@ async function createAICard(config, conversationId, log, options = {}) {
|
|
|
2614
2614
|
upsertPendingCard(aiCardInstance, options.storePath, log);
|
|
2615
2615
|
}
|
|
2616
2616
|
clearAICardDegrade(accountId, log);
|
|
2617
|
+
try {
|
|
2618
|
+
await putAICardStreamingField(aiCardInstance, template.contentKey, "", false, log, {
|
|
2619
|
+
suppressDegrade: true
|
|
2620
|
+
});
|
|
2621
|
+
aiCardInstance.state = AICardStatus.INPUTING;
|
|
2622
|
+
if (shouldPersistPending) {
|
|
2623
|
+
upsertPendingCard(aiCardInstance, options.storePath, log);
|
|
2624
|
+
}
|
|
2625
|
+
} catch (kickErr) {
|
|
2626
|
+
log?.debug?.(
|
|
2627
|
+
`[DingTalk][AICard] Non-critical: failed to kick card into streaming mode: ${kickErr.message}`
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2617
2630
|
return aiCardInstance;
|
|
2618
2631
|
} catch (err) {
|
|
2619
2632
|
log?.error?.(`[DingTalk][AICard] Create failed: ${err.message}`);
|
|
@@ -2712,13 +2725,13 @@ async function clearAICardStreamingContent(card, log) {
|
|
|
2712
2725
|
log?.debug?.(`[DingTalk][AICard] Non-critical: failed to clear streaming content: ${message}`);
|
|
2713
2726
|
}
|
|
2714
2727
|
}
|
|
2715
|
-
async function finalizeAICardStreamingLifecycleIfNeeded(card,
|
|
2728
|
+
async function finalizeAICardStreamingLifecycleIfNeeded(card, log) {
|
|
2716
2729
|
if (!card.streamLifecycleOpened) {
|
|
2717
2730
|
return;
|
|
2718
2731
|
}
|
|
2719
2732
|
const template = DINGTALK_CARD_TEMPLATE;
|
|
2720
2733
|
try {
|
|
2721
|
-
await putAICardStreamingField(card, template.streamingKey,
|
|
2734
|
+
await putAICardStreamingField(card, template.streamingKey, "", true, log, {
|
|
2722
2735
|
suppressDegrade: true
|
|
2723
2736
|
});
|
|
2724
2737
|
} catch (err) {
|
|
@@ -2734,7 +2747,7 @@ async function commitAICardBlocks(card, options, log) {
|
|
|
2734
2747
|
return;
|
|
2735
2748
|
}
|
|
2736
2749
|
await ensureFreshToken(card, log);
|
|
2737
|
-
await finalizeAICardStreamingLifecycleIfNeeded(card,
|
|
2750
|
+
await finalizeAICardStreamingLifecycleIfNeeded(card, log);
|
|
2738
2751
|
const template = DINGTALK_CARD_TEMPLATE;
|
|
2739
2752
|
const updates = {
|
|
2740
2753
|
[template.blockListKey]: options.blockListJson,
|
|
@@ -2797,7 +2810,7 @@ async function finalizeStoppedAICard(card, options, log) {
|
|
|
2797
2810
|
await ensureFreshToken(card, log);
|
|
2798
2811
|
const template = DINGTALK_CARD_TEMPLATE;
|
|
2799
2812
|
const payload = buildStoppedCardFinalizePayload(options);
|
|
2800
|
-
await finalizeAICardStreamingLifecycleIfNeeded(card,
|
|
2813
|
+
await finalizeAICardStreamingLifecycleIfNeeded(card, log);
|
|
2801
2814
|
try {
|
|
2802
2815
|
await updateCardVariables(
|
|
2803
2816
|
card.outTrackId || card.cardInstanceId,
|
|
@@ -4475,7 +4488,7 @@ function stripLeadingInvisibleChars(value) {
|
|
|
4475
4488
|
|
|
4476
4489
|
// src/inbound-handler.ts
|
|
4477
4490
|
import fs6 from "node:fs";
|
|
4478
|
-
import * as
|
|
4491
|
+
import * as path10 from "node:path";
|
|
4479
4492
|
import { isAbortRequestText, isBtwRequestText } from "openclaw/plugin-sdk/reply-runtime";
|
|
4480
4493
|
import { parseInlineDirectives } from "openclaw/plugin-sdk/text-runtime";
|
|
4481
4494
|
|
|
@@ -6716,7 +6729,7 @@ function isMarkdownTableSeparator(line) {
|
|
|
6716
6729
|
return false;
|
|
6717
6730
|
}
|
|
6718
6731
|
const cells = normalized.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
6719
|
-
return cells.length > 0 && cells.every((cell) => /^:?-{
|
|
6732
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{1,}:?$/.test(cell));
|
|
6720
6733
|
}
|
|
6721
6734
|
function isMarkdownTableRow(line) {
|
|
6722
6735
|
const trimmed = line.trim();
|
|
@@ -6725,9 +6738,58 @@ function isMarkdownTableRow(line) {
|
|
|
6725
6738
|
function parseMarkdownTableRow(line) {
|
|
6726
6739
|
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
6727
6740
|
}
|
|
6728
|
-
function
|
|
6729
|
-
const
|
|
6730
|
-
|
|
6741
|
+
function parseSeparatorAlignment(cell) {
|
|
6742
|
+
const trimmed = cell.trim();
|
|
6743
|
+
const hasLeftColon = trimmed.startsWith(":");
|
|
6744
|
+
const hasRightColon = trimmed.endsWith(":");
|
|
6745
|
+
if (hasLeftColon && hasRightColon) {
|
|
6746
|
+
return "center";
|
|
6747
|
+
}
|
|
6748
|
+
if (hasRightColon) {
|
|
6749
|
+
return "right";
|
|
6750
|
+
}
|
|
6751
|
+
if (hasLeftColon) {
|
|
6752
|
+
return "left";
|
|
6753
|
+
}
|
|
6754
|
+
return "center";
|
|
6755
|
+
}
|
|
6756
|
+
function buildSeparatorRow(alignments) {
|
|
6757
|
+
const cells = alignments.map((align) => {
|
|
6758
|
+
if (align === "left") {
|
|
6759
|
+
return ":---";
|
|
6760
|
+
}
|
|
6761
|
+
if (align === "right") {
|
|
6762
|
+
return "---:";
|
|
6763
|
+
}
|
|
6764
|
+
return ":---:";
|
|
6765
|
+
});
|
|
6766
|
+
return "|" + cells.join("|") + "|";
|
|
6767
|
+
}
|
|
6768
|
+
function renderMarkdownTable(headerLine, separatorLine, dataLines) {
|
|
6769
|
+
const headerCells = parseMarkdownTableRow(headerLine);
|
|
6770
|
+
const separatorCells = parseMarkdownTableRow(separatorLine);
|
|
6771
|
+
const dataRows = dataLines.map(parseMarkdownTableRow).filter((cells) => cells.length > 0);
|
|
6772
|
+
if (headerCells.length === 0) {
|
|
6773
|
+
return "";
|
|
6774
|
+
}
|
|
6775
|
+
const colCount = Math.max(
|
|
6776
|
+
headerCells.length,
|
|
6777
|
+
separatorCells.length,
|
|
6778
|
+
...dataRows.map((cells) => cells.length)
|
|
6779
|
+
);
|
|
6780
|
+
const alignments = [];
|
|
6781
|
+
for (let i = 0; i < colCount; i++) {
|
|
6782
|
+
const sepCell = separatorCells[i] || "";
|
|
6783
|
+
alignments.push(parseSeparatorAlignment(sepCell));
|
|
6784
|
+
}
|
|
6785
|
+
const separator = buildSeparatorRow(alignments);
|
|
6786
|
+
const allRows = [headerCells, ...dataRows];
|
|
6787
|
+
const rendered = allRows.map((cells) => {
|
|
6788
|
+
const padded = cells.length < colCount ? [...cells, ...Array(colCount - cells.length).fill("")] : cells;
|
|
6789
|
+
return "|" + padded.join("|") + "|";
|
|
6790
|
+
});
|
|
6791
|
+
rendered.splice(1, 0, separator);
|
|
6792
|
+
return rendered.join("\n");
|
|
6731
6793
|
}
|
|
6732
6794
|
function convertMarkdownTablesToPlainText(text) {
|
|
6733
6795
|
const lines = text.split("\n");
|
|
@@ -6743,13 +6805,20 @@ function convertMarkdownTablesToPlainText(text) {
|
|
|
6743
6805
|
continue;
|
|
6744
6806
|
}
|
|
6745
6807
|
if (!inCodeFence && index + 1 < lines.length && isMarkdownTableRow(line) && isMarkdownTableSeparator(lines[index + 1] || "")) {
|
|
6746
|
-
const
|
|
6808
|
+
const headerLine = line;
|
|
6809
|
+
const separatorLine = lines[index + 1] || "";
|
|
6810
|
+
const dataLines = [];
|
|
6747
6811
|
index += 2;
|
|
6748
6812
|
while (index < lines.length && isMarkdownTableRow(lines[index] || "")) {
|
|
6749
|
-
|
|
6813
|
+
dataLines.push(lines[index] || "");
|
|
6750
6814
|
index += 1;
|
|
6751
6815
|
}
|
|
6752
|
-
|
|
6816
|
+
const renderedTable = renderMarkdownTable(headerLine, separatorLine, dataLines);
|
|
6817
|
+
const lastOutput = output[output.length - 1];
|
|
6818
|
+
if (lastOutput !== void 0 && lastOutput.trim() !== "") {
|
|
6819
|
+
output.push("");
|
|
6820
|
+
}
|
|
6821
|
+
output.push(renderedTable);
|
|
6753
6822
|
continue;
|
|
6754
6823
|
}
|
|
6755
6824
|
output.push(line);
|
|
@@ -7247,6 +7316,9 @@ function buildPersistedOutboundText(text, options) {
|
|
|
7247
7316
|
}
|
|
7248
7317
|
return text;
|
|
7249
7318
|
}
|
|
7319
|
+
function shouldRouteSessionMediaViaProactive(mediaType) {
|
|
7320
|
+
return mediaType === "voice" || mediaType === "video" || mediaType === "file";
|
|
7321
|
+
}
|
|
7250
7322
|
var DINGTALK_TEXT_CHUNK_LIMIT = 3800;
|
|
7251
7323
|
var CARD_MEDIA_CONTROLLER_ATTACH_WAIT_MS = 150;
|
|
7252
7324
|
var CARD_MEDIA_CONTROLLER_ATTACH_POLL_MS = 25;
|
|
@@ -7643,51 +7715,33 @@ async function sendBySession(config, sessionWebhook, text, options = {}) {
|
|
|
7643
7715
|
const token = await getAccessToken(config, options.log);
|
|
7644
7716
|
const log = options.log || getLogger();
|
|
7645
7717
|
if (options.mediaPath && options.mediaType) {
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
const durationMs = uploadedDurationMs ?? await getVoiceDurationMs(options.mediaPath, options.mediaType, log, { preReadBuffer: buffer });
|
|
7656
|
-
body = { msgtype: "voice", voice: { media_id: mediaId, duration: String(durationMs) } };
|
|
7657
|
-
log?.debug?.(
|
|
7658
|
-
`[DingTalk] Sending session voice message mediaId=${mediaId} durationMs=${durationMs}`
|
|
7659
|
-
);
|
|
7660
|
-
} else if (options.mediaType === "video") {
|
|
7661
|
-
body = { msgtype: "video", video: { media_id: mediaId } };
|
|
7662
|
-
} else if (options.mediaType === "file") {
|
|
7663
|
-
body = { msgtype: "file", file: { media_id: mediaId } };
|
|
7664
|
-
}
|
|
7665
|
-
if (body) {
|
|
7666
|
-
const result = await http_client_default({
|
|
7667
|
-
url: sessionWebhook,
|
|
7668
|
-
method: "POST",
|
|
7669
|
-
data: body,
|
|
7670
|
-
headers: { "x-acs-dingtalk-access-token": token, "Content-Type": "application/json" },
|
|
7671
|
-
...getProxyBypassOption(config)
|
|
7672
|
-
});
|
|
7718
|
+
if (options.mediaType === "image") {
|
|
7719
|
+
const uploadResult = await uploadMedia2(config, options.mediaPath, options.mediaType, log, {
|
|
7720
|
+
mediaLocalRoots: options.mediaLocalRoots
|
|
7721
|
+
});
|
|
7722
|
+
if (uploadResult) {
|
|
7723
|
+
const imageMarkdown = ``;
|
|
7724
|
+
text = text ? `${text}
|
|
7725
|
+
|
|
7726
|
+
${imageMarkdown}` : imageMarkdown;
|
|
7673
7727
|
log?.debug?.(
|
|
7674
|
-
`[DingTalk] Session webhook
|
|
7728
|
+
`[DingTalk] Session webhook image will be delivered as markdown media reference mediaId=${uploadResult.mediaId}`
|
|
7675
7729
|
);
|
|
7676
|
-
|
|
7677
|
-
const
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
}
|
|
7683
|
-
return result.data;
|
|
7730
|
+
} else {
|
|
7731
|
+
const mediaHint = options.mediaUrl || options.mediaPath || options.filePath || "(\u5A92\u4F53\u53D1\u9001\u5931\u8D25)";
|
|
7732
|
+
text = `${text}
|
|
7733
|
+
|
|
7734
|
+
\u{1F4CE} \u5A92\u4F53\u53D1\u9001\u5931\u8D25\uFF0C\u515C\u5E95\u94FE\u63A5/\u8DEF\u5F84\uFF1A${mediaHint}`.trim();
|
|
7735
|
+
log?.warn?.("[DingTalk] Media upload failed, falling back to text description");
|
|
7684
7736
|
}
|
|
7685
7737
|
} else {
|
|
7686
7738
|
const mediaHint = options.mediaUrl || options.mediaPath || options.filePath || "(\u5A92\u4F53\u53D1\u9001\u5931\u8D25)";
|
|
7687
7739
|
text = `${text}
|
|
7688
7740
|
|
|
7689
|
-
\u{1F4CE} \
|
|
7690
|
-
log?.warn?.(
|
|
7741
|
+
\u{1F4CE} \u5F53\u524D\u4F1A\u8BDD\u65E0\u6CD5\u76F4\u63A5\u53D1\u9001 ${options.mediaType}\uFF0C\u515C\u5E95\u94FE\u63A5/\u8DEF\u5F84\uFF1A${mediaHint}`.trim();
|
|
7742
|
+
log?.warn?.(
|
|
7743
|
+
`[DingTalk] Session webhook does not support native ${options.mediaType} replies; falling back to text description`
|
|
7744
|
+
);
|
|
7691
7745
|
}
|
|
7692
7746
|
}
|
|
7693
7747
|
const textWithUploadedLocalImages = await replaceMarkdownLocalImages({
|
|
@@ -7733,6 +7787,48 @@ async function sendMessage(config, conversationId, text, options = {}) {
|
|
|
7733
7787
|
try {
|
|
7734
7788
|
const messageType = config.messageType || "markdown";
|
|
7735
7789
|
const log = options.log || getLogger();
|
|
7790
|
+
if (options.sessionWebhook && options.mediaPath && shouldRouteSessionMediaViaProactive(options.mediaType)) {
|
|
7791
|
+
log?.debug?.(
|
|
7792
|
+
`[DingTalk] Session webhook does not support ${options.mediaType} replies reliably; using proactive media API instead`
|
|
7793
|
+
);
|
|
7794
|
+
const proactiveMediaResult = await sendProactiveMedia(
|
|
7795
|
+
config,
|
|
7796
|
+
conversationId,
|
|
7797
|
+
options.mediaPath,
|
|
7798
|
+
options.mediaType,
|
|
7799
|
+
options
|
|
7800
|
+
);
|
|
7801
|
+
if (!proactiveMediaResult.ok) {
|
|
7802
|
+
log?.warn?.(
|
|
7803
|
+
`[DingTalk] Proactive ${options.mediaType} reply failed; falling back to session markdown: ` + (proactiveMediaResult.error || "unknown")
|
|
7804
|
+
);
|
|
7805
|
+
const data = await sendBySession(config, options.sessionWebhook, text, options);
|
|
7806
|
+
const delivery2 = extractOutboundDeliveryMetadata(data);
|
|
7807
|
+
const messageId2 = delivery2.messageId || delivery2.processQueryKey || delivery2.outTrackId;
|
|
7808
|
+
const persistedText = buildPersistedOutboundText(text, options);
|
|
7809
|
+
persistOutboundMessageContext({
|
|
7810
|
+
storePath: options.storePath,
|
|
7811
|
+
accountId: options.accountId,
|
|
7812
|
+
conversationId: options.conversationId || conversationId,
|
|
7813
|
+
text: persistedText,
|
|
7814
|
+
messageType: "outbound-media",
|
|
7815
|
+
quotedRef: options.quotedRef,
|
|
7816
|
+
log,
|
|
7817
|
+
...DEFAULT_OUTBOUND_SENDER,
|
|
7818
|
+
chatType: inferConversationChatType(options.conversationId || conversationId),
|
|
7819
|
+
delivery: {
|
|
7820
|
+
...delivery2,
|
|
7821
|
+
kind: "session"
|
|
7822
|
+
}
|
|
7823
|
+
});
|
|
7824
|
+
return { ok: true, data, messageId: messageId2 };
|
|
7825
|
+
}
|
|
7826
|
+
return {
|
|
7827
|
+
ok: true,
|
|
7828
|
+
data: proactiveMediaResult.data,
|
|
7829
|
+
messageId: proactiveMediaResult.messageId
|
|
7830
|
+
};
|
|
7831
|
+
}
|
|
7736
7832
|
if (messageType === "card" && options.card && !options.forceMarkdown) {
|
|
7737
7833
|
const card = options.card;
|
|
7738
7834
|
if (isCardInTerminalState(card.state)) {
|
|
@@ -7754,26 +7850,6 @@ async function sendMessage(config, conversationId, text, options = {}) {
|
|
|
7754
7850
|
};
|
|
7755
7851
|
}
|
|
7756
7852
|
}
|
|
7757
|
-
if (options.sessionWebhook && options.mediaPath && options.mediaType === "voice") {
|
|
7758
|
-
log?.debug?.(
|
|
7759
|
-
"[DingTalk] Session webhook does not support voice replies reliably; using proactive media API for this voice response"
|
|
7760
|
-
);
|
|
7761
|
-
const proactiveVoiceResult = await sendProactiveMedia(
|
|
7762
|
-
config,
|
|
7763
|
-
conversationId,
|
|
7764
|
-
options.mediaPath,
|
|
7765
|
-
options.mediaType,
|
|
7766
|
-
options
|
|
7767
|
-
);
|
|
7768
|
-
if (!proactiveVoiceResult.ok) {
|
|
7769
|
-
return { ok: false, error: proactiveVoiceResult.error || "Voice reply send failed" };
|
|
7770
|
-
}
|
|
7771
|
-
return {
|
|
7772
|
-
ok: true,
|
|
7773
|
-
data: proactiveVoiceResult.data,
|
|
7774
|
-
messageId: proactiveVoiceResult.messageId
|
|
7775
|
-
};
|
|
7776
|
-
}
|
|
7777
7853
|
if (options.sessionWebhook) {
|
|
7778
7854
|
const data = await sendBySession(config, options.sessionWebhook, text, options);
|
|
7779
7855
|
const delivery2 = extractOutboundDeliveryMetadata(data);
|
|
@@ -9061,8 +9137,6 @@ function createCardDraftController(params) {
|
|
|
9061
9137
|
if (stopped || failed) {
|
|
9062
9138
|
return;
|
|
9063
9139
|
}
|
|
9064
|
-
await contentLoop.flush();
|
|
9065
|
-
await contentLoop.waitForInFlight();
|
|
9066
9140
|
if (hasStreamingContent) {
|
|
9067
9141
|
await clearStreamingContentFromCard();
|
|
9068
9142
|
}
|
|
@@ -9265,6 +9339,7 @@ function createCardDraftController(params) {
|
|
|
9265
9339
|
discardCurrentAnswer();
|
|
9266
9340
|
} else {
|
|
9267
9341
|
sealCurrentAnswer();
|
|
9342
|
+
queueRender();
|
|
9268
9343
|
}
|
|
9269
9344
|
await beginBoundaryFlush();
|
|
9270
9345
|
return;
|
|
@@ -9482,7 +9557,6 @@ function createCardReplyStrategy(ctx) {
|
|
|
9482
9557
|
};
|
|
9483
9558
|
const { mode, usedDeprecatedCardRealTimeStream } = resolveCardStreamingMode(config);
|
|
9484
9559
|
const streamAnswerLive = mode === "answer" || mode === "all";
|
|
9485
|
-
const renderAnswerBlocksLive = mode === "all";
|
|
9486
9560
|
const streamThinkingLive = mode === "all";
|
|
9487
9561
|
let lifecycleState = "open";
|
|
9488
9562
|
const shouldAcceptAnswerSnapshot = () => lifecycleState === "open";
|
|
@@ -9510,6 +9584,7 @@ function createCardReplyStrategy(ctx) {
|
|
|
9510
9584
|
let sawFinalDelivery = false;
|
|
9511
9585
|
let latestReasoningSnapshot = "";
|
|
9512
9586
|
let pendingNonImageMedia = [];
|
|
9587
|
+
const processedMediaUrls = /* @__PURE__ */ new Set();
|
|
9513
9588
|
const getRenderedTimeline = (options = {}) => {
|
|
9514
9589
|
const fallbackAnswer = finalTextForFallback || (sawFinalDelivery ? EMPTY_FINAL_REPLY : void 0);
|
|
9515
9590
|
return controller.getRenderedContent({
|
|
@@ -9616,7 +9691,10 @@ function createCardReplyStrategy(ctx) {
|
|
|
9616
9691
|
finalTextForFallback = normalized.answerText;
|
|
9617
9692
|
return;
|
|
9618
9693
|
}
|
|
9619
|
-
await controller.updateAnswer(normalized.answerText
|
|
9694
|
+
await controller.updateAnswer(normalized.answerText, {
|
|
9695
|
+
stream: streamAnswerLive,
|
|
9696
|
+
renderBlocks: !streamAnswerLive
|
|
9697
|
+
});
|
|
9620
9698
|
}
|
|
9621
9699
|
};
|
|
9622
9700
|
const rewriteLocalMarkdownImagesToPlaceholders = (text) => {
|
|
@@ -9652,7 +9730,8 @@ function createCardReplyStrategy(ctx) {
|
|
|
9652
9730
|
}
|
|
9653
9731
|
await controller.updateAnswer(answerSnapshot, {
|
|
9654
9732
|
stream: streamAnswerLive,
|
|
9655
|
-
|
|
9733
|
+
// Active answer previews live in the content field; blockList is committed at boundaries/finalize.
|
|
9734
|
+
renderBlocks: false
|
|
9656
9735
|
});
|
|
9657
9736
|
};
|
|
9658
9737
|
const applySplitTextToTimeline = async (text, options = {}) => {
|
|
@@ -9674,6 +9753,11 @@ function createCardReplyStrategy(ctx) {
|
|
|
9674
9753
|
if (candidate.classification !== "local") {
|
|
9675
9754
|
continue;
|
|
9676
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
|
+
}
|
|
9677
9761
|
let prepared;
|
|
9678
9762
|
try {
|
|
9679
9763
|
prepared = await prepareMediaInput(candidate.url, log, config.mediaUrlAllowlist);
|
|
@@ -9686,6 +9770,7 @@ function createCardReplyStrategy(ctx) {
|
|
|
9686
9770
|
if (!result?.mediaId) {
|
|
9687
9771
|
continue;
|
|
9688
9772
|
}
|
|
9773
|
+
processedMediaUrls.add(candidate.url.trim());
|
|
9689
9774
|
const placeholder = buildImagePlaceholderText({ alt: candidate.alt, url: candidate.url });
|
|
9690
9775
|
const blockText = candidate.alt.trim() || placeholder.replace(/^见下图/, "").trim() || "\u56FE\u7247";
|
|
9691
9776
|
successfulReroutes.push({
|
|
@@ -9712,6 +9797,11 @@ function createCardReplyStrategy(ctx) {
|
|
|
9712
9797
|
// Card mode keeps runtime block streaming disabled, but still consumes
|
|
9713
9798
|
// reasoning blocks through explicit callbacks and delivery metadata.
|
|
9714
9799
|
disableBlockStreaming: ctx.disableBlockStreaming ?? true,
|
|
9800
|
+
// DingTalk card mode owns the visible reply surface. In group chats,
|
|
9801
|
+
// OpenClaw defaults source replies to message-tool-only; override that
|
|
9802
|
+
// so final replies are delivered into this card instead of spawning a
|
|
9803
|
+
// separate visible message/card via the message tool.
|
|
9804
|
+
sourceReplyDeliveryMode: "automatic",
|
|
9715
9805
|
onAssistantMessageStart: async () => {
|
|
9716
9806
|
if (isLifecycleSealed() || isStopRequested?.()) {
|
|
9717
9807
|
return;
|
|
@@ -9780,6 +9870,10 @@ function createCardReplyStrategy(ctx) {
|
|
|
9780
9870
|
);
|
|
9781
9871
|
if (payload.mediaUrls.length > 0) {
|
|
9782
9872
|
for (const url of payload.mediaUrls) {
|
|
9873
|
+
const normalizedUrl = url.trim();
|
|
9874
|
+
if (processedMediaUrls.has(normalizedUrl)) {
|
|
9875
|
+
continue;
|
|
9876
|
+
}
|
|
9783
9877
|
try {
|
|
9784
9878
|
const prepared = await prepareMediaInput(url, log, config.mediaUrlAllowlist);
|
|
9785
9879
|
const mediaType = resolveOutboundMediaType({ mediaPath: prepared.path, asVoice: false });
|
|
@@ -9794,6 +9888,7 @@ function createCardReplyStrategy(ctx) {
|
|
|
9794
9888
|
const result = await uploadMedia2(config, prepared.path, "image", log);
|
|
9795
9889
|
await prepared.cleanup?.();
|
|
9796
9890
|
if (result?.mediaId) {
|
|
9891
|
+
processedMediaUrls.add(normalizedUrl);
|
|
9797
9892
|
await controller.appendImageBlock(result.mediaId);
|
|
9798
9893
|
}
|
|
9799
9894
|
} catch (err) {
|
|
@@ -9852,6 +9947,10 @@ function createCardReplyStrategy(ctx) {
|
|
|
9852
9947
|
}
|
|
9853
9948
|
if (payload.mediaUrls.length > 0) {
|
|
9854
9949
|
for (const url of payload.mediaUrls) {
|
|
9950
|
+
const normalizedUrl = url.trim();
|
|
9951
|
+
if (processedMediaUrls.has(normalizedUrl)) {
|
|
9952
|
+
continue;
|
|
9953
|
+
}
|
|
9855
9954
|
try {
|
|
9856
9955
|
const prepared = await prepareMediaInput(url, log, config.mediaUrlAllowlist);
|
|
9857
9956
|
const mediaType = resolveOutboundMediaType({ mediaPath: prepared.path, asVoice: false });
|
|
@@ -9864,6 +9963,7 @@ function createCardReplyStrategy(ctx) {
|
|
|
9864
9963
|
const result = await uploadMedia2(config, prepared.path, "image", log);
|
|
9865
9964
|
await prepared.cleanup?.();
|
|
9866
9965
|
if (result?.mediaId) {
|
|
9966
|
+
processedMediaUrls.add(normalizedUrl);
|
|
9867
9967
|
await controller.appendImageBlock(result.mediaId);
|
|
9868
9968
|
}
|
|
9869
9969
|
} catch (err) {
|
|
@@ -9951,6 +10051,7 @@ function createCardReplyStrategy(ctx) {
|
|
|
9951
10051
|
}
|
|
9952
10052
|
try {
|
|
9953
10053
|
await flushPendingReasoning();
|
|
10054
|
+
await controller.clearStreamingContent?.();
|
|
9954
10055
|
await controller.flush();
|
|
9955
10056
|
await controller.waitForInFlight();
|
|
9956
10057
|
const fallbackAnswer = finalTextForFallback || (sawFinalDelivery ? EMPTY_FINAL_REPLY : void 0);
|
|
@@ -10066,6 +10167,7 @@ function createCardReplyStrategy(ctx) {
|
|
|
10066
10167
|
}
|
|
10067
10168
|
|
|
10068
10169
|
// src/reply-strategy-markdown.ts
|
|
10170
|
+
import path8 from "node:path";
|
|
10069
10171
|
var EMPTY_FINAL_FALLBACK_TEXT = "\u2705 Done";
|
|
10070
10172
|
function renderQuotedSegment(text) {
|
|
10071
10173
|
return text.split("\n").map((line) => line.length > 0 ? `> ${line}` : ">").join("\n");
|
|
@@ -10102,6 +10204,10 @@ function computeSharedPrefixTail(previous, next) {
|
|
|
10102
10204
|
const suffix = current.slice(sharedPrefixLength);
|
|
10103
10205
|
return suffix.trim() ? suffix : "";
|
|
10104
10206
|
}
|
|
10207
|
+
function renderMarkdownImage(mediaPath) {
|
|
10208
|
+
const filename = path8.basename(mediaPath) || "image";
|
|
10209
|
+
return ``;
|
|
10210
|
+
}
|
|
10105
10211
|
function createMarkdownReplyStrategy(ctx) {
|
|
10106
10212
|
let finalText;
|
|
10107
10213
|
let activeAnswerText = "";
|
|
@@ -10125,7 +10231,7 @@ function createMarkdownReplyStrategy(ctx) {
|
|
|
10125
10231
|
}
|
|
10126
10232
|
sentVisibleContent = true;
|
|
10127
10233
|
};
|
|
10128
|
-
const
|
|
10234
|
+
const prepareAnswerSuffix = (text) => {
|
|
10129
10235
|
const current = typeof text === "string" ? text : "";
|
|
10130
10236
|
if (current.length > 0) {
|
|
10131
10237
|
activeAnswerText = current;
|
|
@@ -10133,37 +10239,118 @@ function createMarkdownReplyStrategy(ctx) {
|
|
|
10133
10239
|
}
|
|
10134
10240
|
const suffix = computeIncrementalSuffix(lastSentAnswerText, current);
|
|
10135
10241
|
if (suffix) {
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10242
|
+
return {
|
|
10243
|
+
text: suffix,
|
|
10244
|
+
markSent: () => {
|
|
10245
|
+
lastSentAnswerText = current;
|
|
10246
|
+
}
|
|
10247
|
+
};
|
|
10139
10248
|
}
|
|
10140
10249
|
if (current.trim() && lastSentAnswerText && !current.startsWith(lastSentAnswerText)) {
|
|
10141
10250
|
const suffix2 = computeSharedPrefixTail(lastSentAnswerText, current);
|
|
10142
10251
|
ctx.log?.warn?.(
|
|
10143
10252
|
`[DingTalk][Markdown] answer prefix drift detected; falling back to shared-prefix tail prevLen=${lastSentAnswerText.length} currentLen=${current.length}`
|
|
10144
10253
|
);
|
|
10145
|
-
lastSentAnswerText = "";
|
|
10146
10254
|
if (suffix2) {
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10255
|
+
return {
|
|
10256
|
+
text: suffix2,
|
|
10257
|
+
markSent: () => {
|
|
10258
|
+
lastSentAnswerText = current;
|
|
10259
|
+
}
|
|
10260
|
+
};
|
|
10261
|
+
}
|
|
10262
|
+
return {
|
|
10263
|
+
text: current,
|
|
10264
|
+
markSent: () => {
|
|
10265
|
+
lastSentAnswerText = current;
|
|
10266
|
+
}
|
|
10267
|
+
};
|
|
10268
|
+
}
|
|
10269
|
+
return null;
|
|
10270
|
+
};
|
|
10271
|
+
const emitAnswerSuffix = async (text) => {
|
|
10272
|
+
const suffix = prepareAnswerSuffix(text);
|
|
10273
|
+
if (suffix) {
|
|
10274
|
+
await sendMarkdownSegment(suffix.text);
|
|
10275
|
+
suffix.markSent();
|
|
10276
|
+
}
|
|
10277
|
+
};
|
|
10278
|
+
const prepareMarkdownImageAttachments = async (mediaUrls) => {
|
|
10279
|
+
const imageMarkdown = [];
|
|
10280
|
+
const passthroughMediaUrls = [];
|
|
10281
|
+
const cleanups = [];
|
|
10282
|
+
for (const rawMediaUrl of mediaUrls) {
|
|
10283
|
+
const preparedMedia = await prepareMediaInput(
|
|
10284
|
+
rawMediaUrl,
|
|
10285
|
+
ctx.log,
|
|
10286
|
+
ctx.config.mediaUrlAllowlist
|
|
10287
|
+
);
|
|
10288
|
+
const actualMediaPath = preparedMedia.cleanup ? preparedMedia.path : resolveRelativePath(preparedMedia.path);
|
|
10289
|
+
const mediaType = resolveOutboundMediaType({
|
|
10290
|
+
mediaPath: actualMediaPath,
|
|
10291
|
+
asVoice: false
|
|
10292
|
+
});
|
|
10293
|
+
if (mediaType === "image") {
|
|
10294
|
+
imageMarkdown.push(renderMarkdownImage(actualMediaPath));
|
|
10295
|
+
if (preparedMedia.cleanup) {
|
|
10296
|
+
cleanups.push(preparedMedia.cleanup);
|
|
10297
|
+
}
|
|
10298
|
+
} else {
|
|
10299
|
+
await preparedMedia.cleanup?.();
|
|
10300
|
+
passthroughMediaUrls.push(rawMediaUrl);
|
|
10150
10301
|
}
|
|
10151
|
-
await sendMarkdownSegment(current);
|
|
10152
|
-
lastSentAnswerText = current;
|
|
10153
10302
|
}
|
|
10303
|
+
return { imageMarkdown, passthroughMediaUrls, cleanups };
|
|
10154
10304
|
};
|
|
10155
10305
|
return {
|
|
10156
10306
|
getReplyOptions() {
|
|
10157
10307
|
return {
|
|
10158
|
-
disableBlockStreaming: ctx.disableBlockStreaming === true
|
|
10308
|
+
disableBlockStreaming: ctx.disableBlockStreaming === true,
|
|
10309
|
+
// DingTalk markdown/sessionWebhook mode owns the visible reply surface.
|
|
10310
|
+
// Keep runtime final replies on this strategy even when group chats
|
|
10311
|
+
// default source replies to message-tool-only.
|
|
10312
|
+
sourceReplyDeliveryMode: "automatic"
|
|
10159
10313
|
};
|
|
10160
10314
|
},
|
|
10161
10315
|
async deliver(payload) {
|
|
10316
|
+
let answerTextSentWithImages = false;
|
|
10317
|
+
let toolTextSentWithImages = false;
|
|
10162
10318
|
if (payload.mediaUrls.length > 0) {
|
|
10163
|
-
|
|
10164
|
-
|
|
10319
|
+
const prepared = payload.audioAsVoice === true ? {
|
|
10320
|
+
imageMarkdown: [],
|
|
10321
|
+
passthroughMediaUrls: payload.mediaUrls,
|
|
10322
|
+
cleanups: []
|
|
10323
|
+
} : await prepareMarkdownImageAttachments(payload.mediaUrls);
|
|
10324
|
+
try {
|
|
10325
|
+
if (prepared.passthroughMediaUrls.length > 0) {
|
|
10326
|
+
await ctx.deliverMedia(prepared.passthroughMediaUrls, {
|
|
10327
|
+
audioAsVoice: payload.audioAsVoice
|
|
10328
|
+
});
|
|
10329
|
+
sentVisibleContent = true;
|
|
10330
|
+
}
|
|
10331
|
+
if (prepared.imageMarkdown.length > 0) {
|
|
10332
|
+
const answerSuffix = payload.kind === "block" || payload.kind === "final" ? prepareAnswerSuffix(payload.text) : typeof payload.text === "string" ? { text: renderQuotedSegment(payload.text), markSent: () => {
|
|
10333
|
+
} } : null;
|
|
10334
|
+
const markdownParts = [answerSuffix?.text || "", ...prepared.imageMarkdown].filter(
|
|
10335
|
+
(part) => part.trim().length > 0
|
|
10336
|
+
);
|
|
10337
|
+
if (markdownParts.length > 0) {
|
|
10338
|
+
await sendMarkdownSegment(markdownParts.join("\n\n"));
|
|
10339
|
+
answerSuffix?.markSent();
|
|
10340
|
+
}
|
|
10341
|
+
answerTextSentWithImages = payload.kind === "block" || payload.kind === "final";
|
|
10342
|
+
toolTextSentWithImages = payload.kind === "tool";
|
|
10343
|
+
}
|
|
10344
|
+
} finally {
|
|
10345
|
+
for (const cleanup of prepared.cleanups) {
|
|
10346
|
+
await cleanup();
|
|
10347
|
+
}
|
|
10348
|
+
}
|
|
10165
10349
|
}
|
|
10166
10350
|
if (payload.kind === "tool") {
|
|
10351
|
+
if (toolTextSentWithImages) {
|
|
10352
|
+
return;
|
|
10353
|
+
}
|
|
10167
10354
|
const text = typeof payload.text === "string" ? payload.text : "";
|
|
10168
10355
|
if (!text.trim()) {
|
|
10169
10356
|
return;
|
|
@@ -10171,7 +10358,7 @@ function createMarkdownReplyStrategy(ctx) {
|
|
|
10171
10358
|
await sendMarkdownSegment(renderQuotedSegment(text));
|
|
10172
10359
|
return;
|
|
10173
10360
|
}
|
|
10174
|
-
if ((payload.kind === "block" || payload.kind === "final") && typeof payload.text === "string") {
|
|
10361
|
+
if ((payload.kind === "block" || payload.kind === "final") && typeof payload.text === "string" && !answerTextSentWithImages) {
|
|
10175
10362
|
await emitAnswerSuffix(payload.text);
|
|
10176
10363
|
}
|
|
10177
10364
|
},
|
|
@@ -10386,46 +10573,61 @@ function buildAgentSessionKey(params) {
|
|
|
10386
10573
|
identityLinks: cfg.session?.identityLinks
|
|
10387
10574
|
}).toLowerCase();
|
|
10388
10575
|
}
|
|
10389
|
-
function
|
|
10390
|
-
|
|
10391
|
-
}
|
|
10392
|
-
async function resolveSubAgentRoute(params) {
|
|
10393
|
-
const { extractedContent, cfg, isGroup, dingtalkConfig, sessionWebhook, senderId, log } = params;
|
|
10576
|
+
function resolveMessageTarget(params) {
|
|
10577
|
+
const { extractedContent, cfg, isGroup } = params;
|
|
10394
10578
|
const atMentions = extractedContent.atMentions || [];
|
|
10395
|
-
|
|
10579
|
+
if (atMentions.length === 0 || !cfg.agents?.list || cfg.agents.list.length === 0) {
|
|
10580
|
+
return { kind: "default" };
|
|
10581
|
+
}
|
|
10396
10582
|
const textForCommandCheck = extractedContent.text.replace(/^\[引用[^\]]*\]\s*/, "");
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
const isSlashCommand = maybeResolveTextAlias(textWithoutMentions, cfg) !== null;
|
|
10400
|
-
if (atMentions.length === 0 || !cfg.agents?.list || cfg.agents.list.length === 0 || isLearnCommand || isSlashCommand) {
|
|
10401
|
-
return null;
|
|
10583
|
+
if (parseLearnCommand(textForCommandCheck).scope !== "unknown") {
|
|
10584
|
+
return { kind: "default" };
|
|
10402
10585
|
}
|
|
10403
|
-
const
|
|
10586
|
+
const atUserDingtalkIds = isGroup ? extractedContent.atUserDingtalkIds : void 0;
|
|
10587
|
+
const { matchedAgents, unmatchedNames, hasInvalidAgentNames } = resolveAtAgents(
|
|
10404
10588
|
atMentions,
|
|
10405
10589
|
cfg,
|
|
10406
10590
|
atUserDingtalkIds
|
|
10407
10591
|
);
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
try {
|
|
10414
|
-
const sendOptions = isGroup ? { atUserId: senderId, log } : { log };
|
|
10415
|
-
await sendBySession(dingtalkConfig, sessionWebhook, `\u26A0\uFE0F ${fallbackReason}`, {
|
|
10416
|
-
...sendOptions
|
|
10417
|
-
});
|
|
10418
|
-
} catch (err) {
|
|
10419
|
-
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 };
|
|
10420
10597
|
}
|
|
10421
10598
|
}
|
|
10422
|
-
if (matchedAgents.length === 0) {
|
|
10423
|
-
return
|
|
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)}`);
|
|
10424
10615
|
}
|
|
10425
|
-
return { matchedAgents };
|
|
10426
10616
|
}
|
|
10427
10617
|
async function dispatchSubAgents(params) {
|
|
10428
|
-
const {
|
|
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;
|
|
10429
10631
|
let preDownloadedMedia;
|
|
10430
10632
|
const robotCode = resolveRobotCode(dingtalkConfig);
|
|
10431
10633
|
if (robotCode) {
|
|
@@ -10460,18 +10662,17 @@ async function dispatchSubAgents(params) {
|
|
|
10460
10662
|
dingtalkConfig,
|
|
10461
10663
|
subAgentOptions: {
|
|
10462
10664
|
agentId: agentMatch.agentId,
|
|
10463
|
-
responsePrefix: `> \u{1F916} **${sanitizeAgentName(agentMatch.matchedName)}**:
|
|
10665
|
+
responsePrefix: commandText ? "" : `> \u{1F916} **${sanitizeAgentName(agentMatch.matchedName)}**:
|
|
10464
10666
|
|
|
10465
10667
|
`,
|
|
10466
|
-
matchedName: agentMatch.matchedName
|
|
10668
|
+
matchedName: agentMatch.matchedName,
|
|
10669
|
+
commandText
|
|
10467
10670
|
},
|
|
10468
10671
|
preDownloadedMedia
|
|
10469
10672
|
});
|
|
10470
10673
|
} catch (error) {
|
|
10471
10674
|
const message = getErrorMessage(error);
|
|
10472
|
-
log?.error?.(
|
|
10473
|
-
`[DingTalk] Sub-agent ${agentMatch.agentId} failed: ${message}`
|
|
10474
|
-
);
|
|
10675
|
+
log?.error?.(`[DingTalk] Sub-agent ${agentMatch.agentId} failed: ${message}`);
|
|
10475
10676
|
if (error instanceof HostRoutingHelperUnavailableError && !helperMissingWarningSent) {
|
|
10476
10677
|
helperMissingWarningSent = true;
|
|
10477
10678
|
try {
|
|
@@ -10495,12 +10696,12 @@ async function dispatchSubAgents(params) {
|
|
|
10495
10696
|
|
|
10496
10697
|
// src/targeting/group-members-store.ts
|
|
10497
10698
|
import * as fs5 from "node:fs";
|
|
10498
|
-
import * as
|
|
10699
|
+
import * as path9 from "node:path";
|
|
10499
10700
|
var GROUP_MEMBERS_NAMESPACE = "members.group-roster";
|
|
10500
10701
|
function groupMembersFilePath(storePath, groupId) {
|
|
10501
|
-
const dir =
|
|
10702
|
+
const dir = path9.join(path9.dirname(storePath), "dingtalk-members");
|
|
10502
10703
|
const safeId = groupId.replace(/\+/g, "-").replace(/\//g, "_");
|
|
10503
|
-
return
|
|
10704
|
+
return path9.join(dir, `${safeId}.json`);
|
|
10504
10705
|
}
|
|
10505
10706
|
function readLegacyRoster(storePath, groupId) {
|
|
10506
10707
|
const filePath = groupMembersFilePath(storePath, groupId);
|
|
@@ -11191,7 +11392,7 @@ async function handleDingTalkMessage(params) {
|
|
|
11191
11392
|
return;
|
|
11192
11393
|
}
|
|
11193
11394
|
const rawInboundText = extractedContent.text.trim();
|
|
11194
|
-
if (subAgentOptions) {
|
|
11395
|
+
if (subAgentOptions && !subAgentOptions.commandText) {
|
|
11195
11396
|
const cleanText = extractedContent.text.replace(/^\[引用[^\]]*\]\s*/, "");
|
|
11196
11397
|
const contextHint = `[\u4F60\u88AB @ \u4E3A"${subAgentOptions.matchedName}"]
|
|
11197
11398
|
|
|
@@ -11369,36 +11570,62 @@ async function handleDingTalkMessage(params) {
|
|
|
11369
11570
|
peerIdOverride,
|
|
11370
11571
|
config: dingtalkConfig
|
|
11371
11572
|
});
|
|
11372
|
-
const
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
cfg,
|
|
11377
|
-
accountId,
|
|
11573
|
+
const messageTarget = subAgentOptions ? null : resolveMessageTarget({ extractedContent, cfg, isGroup });
|
|
11574
|
+
let route;
|
|
11575
|
+
if (subAgentOptions) {
|
|
11576
|
+
route = {
|
|
11378
11577
|
agentId: subAgentOptions.agentId,
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
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({
|
|
11392
11590
|
cfg,
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
senderId,
|
|
11397
|
-
log
|
|
11591
|
+
channel: "dingtalk",
|
|
11592
|
+
accountId,
|
|
11593
|
+
peer: { kind: sessionPeer.kind, id: sessionPeer.peerId }
|
|
11398
11594
|
});
|
|
11399
|
-
|
|
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
|
+
);
|
|
11400
11627
|
await dispatchSubAgents({
|
|
11401
|
-
|
|
11628
|
+
matchedAgents: messageTarget.matchedAgents,
|
|
11402
11629
|
cfg,
|
|
11403
11630
|
accountId,
|
|
11404
11631
|
data,
|
|
@@ -11422,7 +11649,7 @@ async function handleDingTalkMessage(params) {
|
|
|
11422
11649
|
dingtalkConfig,
|
|
11423
11650
|
senderId,
|
|
11424
11651
|
isDirect,
|
|
11425
|
-
extractedText: extractedContent.text,
|
|
11652
|
+
extractedText: subAgentOptions?.commandText ?? extractedContent.text,
|
|
11426
11653
|
messageType: extractedContent.messageType,
|
|
11427
11654
|
data: {
|
|
11428
11655
|
conversationId: data.conversationId,
|
|
@@ -11581,7 +11808,7 @@ async function handleDingTalkMessage(params) {
|
|
|
11581
11808
|
if (!hasPathLikeShape) {
|
|
11582
11809
|
return void 0;
|
|
11583
11810
|
}
|
|
11584
|
-
return STANDALONE_MEDIA_PATH_EXTENSIONS.has(
|
|
11811
|
+
return STANDALONE_MEDIA_PATH_EXTENSIONS.has(path10.extname(trimmed).toLowerCase()) ? trimmed : void 0;
|
|
11585
11812
|
}, extractSharedAudioAsVoice = function(payload, inlineReplyPayload) {
|
|
11586
11813
|
const richPayload = payload;
|
|
11587
11814
|
const sharedValue = parseBooleanLike(richPayload.audioAsVoice);
|
|
@@ -12045,6 +12272,7 @@ ${extracted.text}`;
|
|
|
12045
12272
|
const inboundText = attachmentExtractedText ? `${inboundBody.trimEnd()}
|
|
12046
12273
|
|
|
12047
12274
|
${attachmentExtractedText}` : inboundBody;
|
|
12275
|
+
const commandBody = subAgentOptions?.commandText ?? inboundText;
|
|
12048
12276
|
const learningEnabled = isLearningEnabled(dingtalkConfig);
|
|
12049
12277
|
const learningContextBlock = buildLearningContextBlock({
|
|
12050
12278
|
enabled: learningEnabled,
|
|
@@ -12086,7 +12314,7 @@ ${attachmentExtractedText}` : inboundBody;
|
|
|
12086
12314
|
const ctx = rt.channel.reply.finalizeInboundContext({
|
|
12087
12315
|
Body: body,
|
|
12088
12316
|
RawBody: inboundText,
|
|
12089
|
-
CommandBody:
|
|
12317
|
+
CommandBody: commandBody,
|
|
12090
12318
|
QuotedRef: quotedRef,
|
|
12091
12319
|
QuotedRefJson: quotedRef ? JSON.stringify(quotedRef) : void 0,
|
|
12092
12320
|
ReplyToId: quotedRuntimeContext?.replyToId,
|
|
@@ -13025,14 +13253,14 @@ var RegistrationError = class extends Error {
|
|
|
13025
13253
|
function asString(value) {
|
|
13026
13254
|
return typeof value === "string" ? value : "";
|
|
13027
13255
|
}
|
|
13028
|
-
async function apiPost(
|
|
13029
|
-
const url = `${REGISTRATION_BASE_URL}${
|
|
13256
|
+
async function apiPost(path11, payload) {
|
|
13257
|
+
const url = `${REGISTRATION_BASE_URL}${path11}`;
|
|
13030
13258
|
const resp = await http_client_default.post(url, payload, { timeout: 15e3 });
|
|
13031
13259
|
const data = resp.data;
|
|
13032
13260
|
const errcode = data.errcode;
|
|
13033
13261
|
if (errcode !== void 0 && errcode !== 0) {
|
|
13034
13262
|
const errmsg = asString(data.errmsg) || "unknown error";
|
|
13035
|
-
throw new RegistrationError(`API error [${
|
|
13263
|
+
throw new RegistrationError(`API error [${path11}]: ${errmsg} (errcode=${typeof errcode === "number" ? errcode : asString(errcode)})`);
|
|
13036
13264
|
}
|
|
13037
13265
|
return data;
|
|
13038
13266
|
}
|