@openclaw-china/dingtalk 2026.3.18 → 2026.3.19
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.d.ts +21 -1
- package/dist/index.js +228 -151
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -883,7 +883,8 @@ declare function sendMessageDingtalk(params: SendMessageParams): Promise<Dingtal
|
|
|
883
883
|
*
|
|
884
884
|
* 包含 Moltbot 核心 API,用于:
|
|
885
885
|
* - 路由解析 (channel.routing.resolveAgentRoute)
|
|
886
|
-
* -
|
|
886
|
+
* - 实时消息分发 (channel.reply.dispatchReplyWithDispatcher /
|
|
887
|
+
* channel.reply.dispatchReplyWithBufferedBlockDispatcher)
|
|
887
888
|
* - 系统事件 (system.enqueueSystemEvent)
|
|
888
889
|
*/
|
|
889
890
|
interface PluginRuntime {
|
|
@@ -926,6 +927,25 @@ interface PluginRuntime {
|
|
|
926
927
|
}) => Promise<void>;
|
|
927
928
|
};
|
|
928
929
|
reply?: {
|
|
930
|
+
dispatchReplyWithDispatcher?: (params: {
|
|
931
|
+
ctx: unknown;
|
|
932
|
+
cfg: unknown;
|
|
933
|
+
dispatcherOptions: {
|
|
934
|
+
deliver: (payload: unknown, info?: {
|
|
935
|
+
kind?: string;
|
|
936
|
+
}) => Promise<void>;
|
|
937
|
+
onError?: (err: unknown, info: {
|
|
938
|
+
kind: string;
|
|
939
|
+
}) => void;
|
|
940
|
+
onSkip?: (payload: unknown, info: {
|
|
941
|
+
kind: string;
|
|
942
|
+
reason: string;
|
|
943
|
+
}) => void;
|
|
944
|
+
onReplyStart?: () => Promise<void> | void;
|
|
945
|
+
humanDelay?: unknown;
|
|
946
|
+
};
|
|
947
|
+
replyOptions?: unknown;
|
|
948
|
+
}) => Promise<unknown>;
|
|
929
949
|
dispatchReplyFromConfig?: (params: {
|
|
930
950
|
ctx: unknown;
|
|
931
951
|
cfg: unknown;
|
package/dist/index.js
CHANGED
|
@@ -654,8 +654,8 @@ function getErrorMap() {
|
|
|
654
654
|
|
|
655
655
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
656
656
|
var makeIssue = (params) => {
|
|
657
|
-
const { data, path:
|
|
658
|
-
const fullPath = [...
|
|
657
|
+
const { data, path: path6, errorMaps, issueData } = params;
|
|
658
|
+
const fullPath = [...path6, ...issueData.path || []];
|
|
659
659
|
const fullIssue = {
|
|
660
660
|
...issueData,
|
|
661
661
|
path: fullPath
|
|
@@ -771,11 +771,11 @@ var errorUtil;
|
|
|
771
771
|
|
|
772
772
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
773
773
|
var ParseInputLazyPath = class {
|
|
774
|
-
constructor(parent, value,
|
|
774
|
+
constructor(parent, value, path6, key) {
|
|
775
775
|
this._cachedPath = [];
|
|
776
776
|
this.parent = parent;
|
|
777
777
|
this.data = value;
|
|
778
|
-
this._path =
|
|
778
|
+
this._path = path6;
|
|
779
779
|
this._key = key;
|
|
780
780
|
}
|
|
781
781
|
get path() {
|
|
@@ -6378,6 +6378,7 @@ var CHANNEL_ORDER = [
|
|
|
6378
6378
|
"wecom",
|
|
6379
6379
|
"wecom-app",
|
|
6380
6380
|
"wecom-kf",
|
|
6381
|
+
"wechat-mp",
|
|
6381
6382
|
"feishu-china"
|
|
6382
6383
|
];
|
|
6383
6384
|
var CHANNEL_DISPLAY_LABELS = {
|
|
@@ -6386,6 +6387,7 @@ var CHANNEL_DISPLAY_LABELS = {
|
|
|
6386
6387
|
wecom: "WeCom\uFF08\u4F01\u4E1A\u5FAE\u4FE1-\u667A\u80FD\u673A\u5668\u4EBA\uFF09",
|
|
6387
6388
|
"wecom-app": "WeCom App\uFF08\u81EA\u5EFA\u5E94\u7528-\u53EF\u63A5\u5165\u5FAE\u4FE1\uFF09",
|
|
6388
6389
|
"wecom-kf": "WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09",
|
|
6390
|
+
"wechat-mp": "WeChat MP\uFF08\u5FAE\u4FE1\u516C\u4F17\u53F7\uFF09",
|
|
6389
6391
|
qqbot: "QQBot\uFF08QQ \u673A\u5668\u4EBA\uFF09"
|
|
6390
6392
|
};
|
|
6391
6393
|
var CHANNEL_GUIDE_LINKS = {
|
|
@@ -6394,6 +6396,7 @@ var CHANNEL_GUIDE_LINKS = {
|
|
|
6394
6396
|
wecom: `${GUIDES_BASE}/wecom/configuration.md`,
|
|
6395
6397
|
"wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
|
|
6396
6398
|
"wecom-kf": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/extensions/wecom-kf/README.md",
|
|
6399
|
+
"wechat-mp": `${GUIDES_BASE}/wechat-mp/configuration.md`,
|
|
6397
6400
|
qqbot: `${GUIDES_BASE}/qqbot/configuration.md`
|
|
6398
6401
|
};
|
|
6399
6402
|
var CHINA_CLI_STATE_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-cli-state");
|
|
@@ -6603,7 +6606,9 @@ function isChannelConfigured(cfg, channelId) {
|
|
|
6603
6606
|
case "wecom-app":
|
|
6604
6607
|
return hasTokenPair(channelCfg);
|
|
6605
6608
|
case "wecom-kf":
|
|
6606
|
-
return hasNonEmptyString(channelCfg.corpId) && hasNonEmptyString(channelCfg.
|
|
6609
|
+
return hasNonEmptyString(channelCfg.corpId) && hasNonEmptyString(channelCfg.token) && hasNonEmptyString(channelCfg.encodingAESKey);
|
|
6610
|
+
case "wechat-mp":
|
|
6611
|
+
return hasNonEmptyString(channelCfg.appId) && hasNonEmptyString(channelCfg.token);
|
|
6607
6612
|
default:
|
|
6608
6613
|
return false;
|
|
6609
6614
|
}
|
|
@@ -6864,6 +6869,15 @@ async function configureWecomKf(prompter, cfg) {
|
|
|
6864
6869
|
section("\u914D\u7F6E WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09");
|
|
6865
6870
|
showGuideLink("wecom-kf");
|
|
6866
6871
|
const existing = getChannelConfig(cfg, "wecom-kf");
|
|
6872
|
+
Ve(
|
|
6873
|
+
[
|
|
6874
|
+
"\u5411\u5BFC\u987A\u5E8F\uFF1AwebhookPath / token / encodingAESKey / corpId / open_kfid / corpSecret",
|
|
6875
|
+
"\u57FA\u7840\u5FC5\u586B\uFF1AcorpId / token / encodingAESKey / open_kfid",
|
|
6876
|
+
"corpSecret \u4F1A\u4F5C\u4E3A\u6700\u540E\u4E00\u4E2A\u53C2\u6570\u8BE2\u95EE\uFF1B\u9996\u6B21\u63A5\u5165\u53EF\u5148\u7559\u7A7A\uFF0C\u5F85\u56DE\u8C03 URL \u6821\u9A8C\u901A\u8FC7\u5E76\u70B9\u51FB\u201C\u5F00\u59CB\u4F7F\u7528\u201D\u540E\u518D\u8865",
|
|
6877
|
+
"webhookPath \u9ED8\u8BA4\u503C\uFF1A/wecom-kf"
|
|
6878
|
+
].join("\n"),
|
|
6879
|
+
"\u53C2\u6570\u8BF4\u660E"
|
|
6880
|
+
);
|
|
6867
6881
|
const webhookPath = await prompter.askText({
|
|
6868
6882
|
label: "Webhook \u8DEF\u5F84\uFF08\u9ED8\u8BA4 /wecom-kf\uFF09",
|
|
6869
6883
|
defaultValue: toTrimmedString2(existing.webhookPath) ?? "/wecom-kf",
|
|
@@ -6884,19 +6898,14 @@ async function configureWecomKf(prompter, cfg) {
|
|
|
6884
6898
|
defaultValue: toTrimmedString2(existing.corpId),
|
|
6885
6899
|
required: true
|
|
6886
6900
|
});
|
|
6887
|
-
const corpSecret = await prompter.askSecret({
|
|
6888
|
-
label: "\u5FAE\u4FE1\u5BA2\u670D Secret",
|
|
6889
|
-
existingValue: toTrimmedString2(existing.corpSecret),
|
|
6890
|
-
required: true
|
|
6891
|
-
});
|
|
6892
6901
|
const openKfId = await prompter.askText({
|
|
6893
6902
|
label: "open_kfid",
|
|
6894
6903
|
defaultValue: toTrimmedString2(existing.openKfId),
|
|
6895
6904
|
required: true
|
|
6896
6905
|
});
|
|
6897
|
-
const
|
|
6898
|
-
label: "\
|
|
6899
|
-
|
|
6906
|
+
const corpSecret = await prompter.askSecret({
|
|
6907
|
+
label: "\u5FAE\u4FE1\u5BA2\u670D Secret\uFF08\u6700\u540E\u586B\u5199\uFF1B\u9996\u6B21\u63A5\u5165\u53EF\u5148\u7559\u7A7A\uFF09",
|
|
6908
|
+
existingValue: toTrimmedString2(existing.corpSecret),
|
|
6900
6909
|
required: false
|
|
6901
6910
|
});
|
|
6902
6911
|
return mergeChannelConfig(cfg, "wecom-kf", {
|
|
@@ -6904,8 +6913,72 @@ async function configureWecomKf(prompter, cfg) {
|
|
|
6904
6913
|
token,
|
|
6905
6914
|
encodingAESKey,
|
|
6906
6915
|
corpId,
|
|
6907
|
-
corpSecret,
|
|
6908
6916
|
openKfId,
|
|
6917
|
+
corpSecret: corpSecret || void 0
|
|
6918
|
+
});
|
|
6919
|
+
}
|
|
6920
|
+
async function configureWechatMp(prompter, cfg) {
|
|
6921
|
+
section("\u914D\u7F6E WeChat MP\uFF08\u5FAE\u4FE1\u516C\u4F17\u53F7\uFF09");
|
|
6922
|
+
showGuideLink("wechat-mp");
|
|
6923
|
+
const existing = getChannelConfig(cfg, "wechat-mp");
|
|
6924
|
+
const webhookPath = await prompter.askText({
|
|
6925
|
+
label: "Webhook \u8DEF\u5F84\uFF08\u9ED8\u8BA4 /wechat-mp\uFF09",
|
|
6926
|
+
defaultValue: toTrimmedString2(existing.webhookPath) ?? "/wechat-mp",
|
|
6927
|
+
required: true
|
|
6928
|
+
});
|
|
6929
|
+
const appId = await prompter.askText({
|
|
6930
|
+
label: "\u516C\u4F17\u53F7 appId",
|
|
6931
|
+
defaultValue: toTrimmedString2(existing.appId),
|
|
6932
|
+
required: true
|
|
6933
|
+
});
|
|
6934
|
+
const appSecret = await prompter.askSecret({
|
|
6935
|
+
label: "\u516C\u4F17\u53F7 appSecret\uFF08\u4E3B\u52A8\u53D1\u9001\u9700\u8981\uFF09",
|
|
6936
|
+
existingValue: toTrimmedString2(existing.appSecret),
|
|
6937
|
+
required: false
|
|
6938
|
+
});
|
|
6939
|
+
const token = await prompter.askSecret({
|
|
6940
|
+
label: "\u670D\u52A1\u5668\u914D\u7F6E token",
|
|
6941
|
+
existingValue: toTrimmedString2(existing.token),
|
|
6942
|
+
required: true
|
|
6943
|
+
});
|
|
6944
|
+
const messageMode = await prompter.askSelect(
|
|
6945
|
+
"\u6D88\u606F\u52A0\u89E3\u5BC6\u6A21\u5F0F",
|
|
6946
|
+
[
|
|
6947
|
+
{ value: "plain", label: "plain\uFF08\u660E\u6587\uFF09" },
|
|
6948
|
+
{ value: "safe", label: "safe\uFF08\u5B89\u5168\u6A21\u5F0F\uFF09" },
|
|
6949
|
+
{ value: "compat", label: "compat\uFF08\u517C\u5BB9\u6A21\u5F0F\uFF09" }
|
|
6950
|
+
],
|
|
6951
|
+
toTrimmedString2(existing.messageMode) ?? "safe"
|
|
6952
|
+
);
|
|
6953
|
+
let encodingAESKey = toTrimmedString2(existing.encodingAESKey);
|
|
6954
|
+
if (messageMode !== "plain") {
|
|
6955
|
+
encodingAESKey = await prompter.askSecret({
|
|
6956
|
+
label: "EncodingAESKey\uFF08safe/compat \u5FC5\u586B\uFF09",
|
|
6957
|
+
existingValue: encodingAESKey,
|
|
6958
|
+
required: true
|
|
6959
|
+
});
|
|
6960
|
+
}
|
|
6961
|
+
const replyMode = await prompter.askSelect(
|
|
6962
|
+
"\u56DE\u590D\u6A21\u5F0F",
|
|
6963
|
+
[
|
|
6964
|
+
{ value: "passive", label: "passive\uFF085 \u79D2\u5185\u88AB\u52A8\u56DE\u590D\uFF09" },
|
|
6965
|
+
{ value: "active", label: "active\uFF08\u5BA2\u670D\u6D88\u606F\u4E3B\u52A8\u53D1\u9001\uFF09" }
|
|
6966
|
+
],
|
|
6967
|
+
toTrimmedString2(existing.replyMode) ?? "passive"
|
|
6968
|
+
);
|
|
6969
|
+
const welcomeText = await prompter.askText({
|
|
6970
|
+
label: "\u6B22\u8FCE\u8BED\uFF08\u53EF\u9009\uFF09",
|
|
6971
|
+
defaultValue: toTrimmedString2(existing.welcomeText),
|
|
6972
|
+
required: false
|
|
6973
|
+
});
|
|
6974
|
+
return mergeChannelConfig(cfg, "wechat-mp", {
|
|
6975
|
+
webhookPath,
|
|
6976
|
+
appId,
|
|
6977
|
+
appSecret: appSecret || void 0,
|
|
6978
|
+
token,
|
|
6979
|
+
encodingAESKey: messageMode === "plain" ? void 0 : encodingAESKey,
|
|
6980
|
+
messageMode,
|
|
6981
|
+
replyMode,
|
|
6909
6982
|
welcomeText: welcomeText || void 0
|
|
6910
6983
|
});
|
|
6911
6984
|
}
|
|
@@ -6967,6 +7040,8 @@ async function configureSingleChannel(channel, prompter, cfg) {
|
|
|
6967
7040
|
return configureWecomApp(prompter, cfg);
|
|
6968
7041
|
case "wecom-kf":
|
|
6969
7042
|
return configureWecomKf(prompter, cfg);
|
|
7043
|
+
case "wechat-mp":
|
|
7044
|
+
return configureWechatMp(prompter, cfg);
|
|
6970
7045
|
case "qqbot":
|
|
6971
7046
|
return configureQQBot(prompter, cfg);
|
|
6972
7047
|
default:
|
|
@@ -7108,6 +7183,7 @@ var SUPPORTED_CHANNELS = [
|
|
|
7108
7183
|
"wecom",
|
|
7109
7184
|
"wecom-app",
|
|
7110
7185
|
"wecom-kf",
|
|
7186
|
+
"wechat-mp",
|
|
7111
7187
|
"qqbot"
|
|
7112
7188
|
];
|
|
7113
7189
|
var CHINA_INSTALL_HINT_SHOWN_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
@@ -8251,6 +8327,52 @@ ${list}`;
|
|
|
8251
8327
|
|
|
8252
8328
|
${prompt}` : content;
|
|
8253
8329
|
}
|
|
8330
|
+
function escapeRegExp(value) {
|
|
8331
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8332
|
+
}
|
|
8333
|
+
function finalizeReplyText(text) {
|
|
8334
|
+
return text.replace(/\n{3,}/g, "\n\n").trim();
|
|
8335
|
+
}
|
|
8336
|
+
function stripLocalMediaSyntaxFromText(text, mediaItems) {
|
|
8337
|
+
let result = text;
|
|
8338
|
+
for (const media of mediaItems) {
|
|
8339
|
+
const source = media.source?.trim();
|
|
8340
|
+
if (!source) continue;
|
|
8341
|
+
const escapedSource = escapeRegExp(source);
|
|
8342
|
+
const fileName = media.fileName ?? path.basename(media.localPath ?? source);
|
|
8343
|
+
if (media.type === "image") {
|
|
8344
|
+
const pattern = new RegExp(`\\[!\\[[^\\]]*\\]\\(${escapedSource}\\)\\]\\([^\\)]+\\)`, "g");
|
|
8345
|
+
result = result.replace(pattern, "");
|
|
8346
|
+
}
|
|
8347
|
+
if (media.sourceKind === "markdown") {
|
|
8348
|
+
if (media.type === "image") {
|
|
8349
|
+
const pattern = new RegExp(`!\\[[^\\]]*\\]\\(${escapedSource}\\)`, "g");
|
|
8350
|
+
result = result.replace(pattern, "");
|
|
8351
|
+
} else {
|
|
8352
|
+
const pattern = new RegExp(`\\[[^\\]]*\\]\\(${escapedSource}\\)`, "g");
|
|
8353
|
+
result = result.replace(pattern, `[\u6587\u4EF6: ${fileName}]`);
|
|
8354
|
+
}
|
|
8355
|
+
continue;
|
|
8356
|
+
}
|
|
8357
|
+
if (result.includes(source)) {
|
|
8358
|
+
const replacement = media.type === "image" ? "" : `[\u6587\u4EF6: ${fileName}]`;
|
|
8359
|
+
result = result.split(source).join(replacement);
|
|
8360
|
+
}
|
|
8361
|
+
}
|
|
8362
|
+
return finalizeReplyText(result);
|
|
8363
|
+
}
|
|
8364
|
+
function dedupeMediaUrls(values) {
|
|
8365
|
+
const mediaQueue = [];
|
|
8366
|
+
const seenMedia = /* @__PURE__ */ new Set();
|
|
8367
|
+
for (const value of values) {
|
|
8368
|
+
const trimmed = value?.trim();
|
|
8369
|
+
if (!trimmed) continue;
|
|
8370
|
+
if (seenMedia.has(trimmed)) continue;
|
|
8371
|
+
seenMedia.add(trimmed);
|
|
8372
|
+
mediaQueue.push(trimmed);
|
|
8373
|
+
}
|
|
8374
|
+
return mediaQueue;
|
|
8375
|
+
}
|
|
8254
8376
|
function extractLocalMediaFromText(params) {
|
|
8255
8377
|
const { text, logger } = params;
|
|
8256
8378
|
const result = extractMediaFromText(text, {
|
|
@@ -8270,13 +8392,17 @@ function extractLocalMediaFromText(params) {
|
|
|
8270
8392
|
parseBarePaths: true,
|
|
8271
8393
|
parseMarkdownLinks: true
|
|
8272
8394
|
});
|
|
8273
|
-
const
|
|
8274
|
-
|
|
8395
|
+
const localMedia = result.all.filter((m) => m.isLocal && m.localPath);
|
|
8396
|
+
const mediaUrls = localMedia.map((m) => m.localPath);
|
|
8397
|
+
return {
|
|
8398
|
+
text: stripLocalMediaSyntaxFromText(text, localMedia),
|
|
8399
|
+
mediaUrls
|
|
8400
|
+
};
|
|
8275
8401
|
}
|
|
8276
8402
|
function extractMediaLinesFromText(params) {
|
|
8277
8403
|
const { text, logger } = params;
|
|
8278
8404
|
const result = extractMediaFromText(text, {
|
|
8279
|
-
removeFromText:
|
|
8405
|
+
removeFromText: true,
|
|
8280
8406
|
checkExists: true,
|
|
8281
8407
|
existsSync: (p) => {
|
|
8282
8408
|
const exists = fs3.existsSync(p);
|
|
@@ -8294,6 +8420,18 @@ function extractMediaLinesFromText(params) {
|
|
|
8294
8420
|
const mediaUrls = result.all.map((m) => m.isLocal ? m.localPath ?? m.source : m.source).filter((m) => typeof m === "string" && m.trim().length > 0);
|
|
8295
8421
|
return { text: result.text, mediaUrls };
|
|
8296
8422
|
}
|
|
8423
|
+
function prepareDingtalkReplyContent(params) {
|
|
8424
|
+
const { text, logger } = params;
|
|
8425
|
+
const mediaLineResult = extractMediaLinesFromText({ text, logger });
|
|
8426
|
+
const localMediaResult = extractLocalMediaFromText({
|
|
8427
|
+
text: mediaLineResult.text,
|
|
8428
|
+
logger
|
|
8429
|
+
});
|
|
8430
|
+
return {
|
|
8431
|
+
text: localMediaResult.text,
|
|
8432
|
+
mediaUrls: dedupeMediaUrls([...mediaLineResult.mediaUrls, ...localMediaResult.mediaUrls])
|
|
8433
|
+
};
|
|
8434
|
+
}
|
|
8297
8435
|
function resolveAudioRecognition(raw) {
|
|
8298
8436
|
if (raw.msgtype !== "audio") return void 0;
|
|
8299
8437
|
if (!raw.content) return void 0;
|
|
@@ -8313,12 +8451,12 @@ function resolveAudioRecognition(raw) {
|
|
|
8313
8451
|
function resolveGatewayAuthFromConfigFile(logger) {
|
|
8314
8452
|
try {
|
|
8315
8453
|
const fs5 = __require("fs");
|
|
8316
|
-
const
|
|
8454
|
+
const path6 = __require("path");
|
|
8317
8455
|
const os4 = __require("os");
|
|
8318
8456
|
const home = os4.homedir();
|
|
8319
8457
|
const candidates = [
|
|
8320
|
-
|
|
8321
|
-
|
|
8458
|
+
path6.join(home, ".openclaw", "openclaw.json"),
|
|
8459
|
+
path6.join(home, ".openclaw", "config.json")
|
|
8322
8460
|
];
|
|
8323
8461
|
for (const filePath of candidates) {
|
|
8324
8462
|
if (!fs5.existsSync(filePath)) continue;
|
|
@@ -8532,35 +8670,21 @@ async function handleAICardStreaming(params) {
|
|
|
8532
8670
|
}
|
|
8533
8671
|
const now = Date.now();
|
|
8534
8672
|
if (!firstFrameSent || now - lastUpdateTime >= updateInterval) {
|
|
8535
|
-
|
|
8673
|
+
const previewText = prepareDingtalkReplyContent({ text: accumulated }).text;
|
|
8674
|
+
await streamAICard(card, previewText, false);
|
|
8536
8675
|
lastUpdateTime = now;
|
|
8537
8676
|
firstFrameSent = true;
|
|
8538
8677
|
}
|
|
8539
8678
|
}
|
|
8540
|
-
|
|
8541
|
-
logger.info(`AI Card streaming completed with ${accumulated.length} chars`);
|
|
8542
|
-
const { mediaUrls: mediaFromLines } = extractMediaLinesFromText({
|
|
8679
|
+
const preparedReply = prepareDingtalkReplyContent({
|
|
8543
8680
|
text: accumulated,
|
|
8544
8681
|
logger
|
|
8545
8682
|
});
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
const seenMedia = /* @__PURE__ */ new Set();
|
|
8552
|
-
const addMedia = (value) => {
|
|
8553
|
-
const trimmed = value?.trim();
|
|
8554
|
-
if (!trimmed) return;
|
|
8555
|
-
if (seenMedia.has(trimmed)) return;
|
|
8556
|
-
seenMedia.add(trimmed);
|
|
8557
|
-
mediaQueue.push(trimmed);
|
|
8558
|
-
};
|
|
8559
|
-
for (const url of mediaFromLines) addMedia(url);
|
|
8560
|
-
for (const url of localMediaFromText) addMedia(url);
|
|
8561
|
-
if (mediaQueue.length > 0) {
|
|
8562
|
-
logger.debug(`[stream] sending ${mediaQueue.length} media attachments`);
|
|
8563
|
-
for (const mediaUrl of mediaQueue) {
|
|
8683
|
+
await finishAICard(card, preparedReply.text, (msg) => logger.debug(msg));
|
|
8684
|
+
logger.info(`AI Card streaming completed with ${preparedReply.text.length} chars`);
|
|
8685
|
+
if (preparedReply.mediaUrls.length > 0) {
|
|
8686
|
+
logger.debug(`[stream] sending ${preparedReply.mediaUrls.length} media attachments`);
|
|
8687
|
+
for (const mediaUrl of preparedReply.mediaUrls) {
|
|
8564
8688
|
try {
|
|
8565
8689
|
await sendMediaDingtalk({
|
|
8566
8690
|
cfg: dingtalkCfg,
|
|
@@ -8584,9 +8708,13 @@ async function handleAICardStreaming(params) {
|
|
|
8584
8708
|
}
|
|
8585
8709
|
try {
|
|
8586
8710
|
const fallbackText = accumulated.trim() ? accumulated : formatStreamingInterruptMessage(err);
|
|
8711
|
+
const preparedReply = prepareDingtalkReplyContent({
|
|
8712
|
+
text: fallbackText,
|
|
8713
|
+
logger
|
|
8714
|
+
});
|
|
8587
8715
|
const limit = dingtalkCfg.textChunkLimit ?? 4e3;
|
|
8588
|
-
for (let i = 0; i <
|
|
8589
|
-
const chunk =
|
|
8716
|
+
for (let i = 0; i < preparedReply.text.length; i += limit) {
|
|
8717
|
+
const chunk = preparedReply.text.slice(i, i + limit);
|
|
8590
8718
|
await sendMessageDingtalk({
|
|
8591
8719
|
cfg: dingtalkCfg,
|
|
8592
8720
|
to: targetId,
|
|
@@ -8594,26 +8722,7 @@ async function handleAICardStreaming(params) {
|
|
|
8594
8722
|
chatType
|
|
8595
8723
|
});
|
|
8596
8724
|
}
|
|
8597
|
-
const
|
|
8598
|
-
text: fallbackText,
|
|
8599
|
-
logger
|
|
8600
|
-
});
|
|
8601
|
-
const { mediaUrls: localMediaFromText } = extractLocalMediaFromText({
|
|
8602
|
-
text: fallbackText,
|
|
8603
|
-
logger
|
|
8604
|
-
});
|
|
8605
|
-
const mediaQueue = [];
|
|
8606
|
-
const seenMedia = /* @__PURE__ */ new Set();
|
|
8607
|
-
const addMedia = (value) => {
|
|
8608
|
-
const trimmed = value?.trim();
|
|
8609
|
-
if (!trimmed) return;
|
|
8610
|
-
if (seenMedia.has(trimmed)) return;
|
|
8611
|
-
seenMedia.add(trimmed);
|
|
8612
|
-
mediaQueue.push(trimmed);
|
|
8613
|
-
};
|
|
8614
|
-
for (const url of mediaFromLines) addMedia(url);
|
|
8615
|
-
for (const url of localMediaFromText) addMedia(url);
|
|
8616
|
-
for (const mediaUrl of mediaQueue) {
|
|
8725
|
+
for (const mediaUrl of preparedReply.mediaUrls) {
|
|
8617
8726
|
await sendMediaDingtalk({
|
|
8618
8727
|
cfg: dingtalkCfg,
|
|
8619
8728
|
to: targetId,
|
|
@@ -8748,12 +8857,8 @@ async function handleDingtalkMessage(params) {
|
|
|
8748
8857
|
logger.debug("core.channel.routing.resolveAgentRoute not available, skipping dispatch");
|
|
8749
8858
|
return;
|
|
8750
8859
|
}
|
|
8751
|
-
if (!replyApi?.
|
|
8752
|
-
logger.
|
|
8753
|
-
return;
|
|
8754
|
-
}
|
|
8755
|
-
if (!replyApi?.createReplyDispatcher && !replyApi?.createReplyDispatcherWithTyping) {
|
|
8756
|
-
logger.debug("core.channel.reply dispatcher factory not available, skipping dispatch");
|
|
8860
|
+
if (!replyApi?.dispatchReplyWithDispatcher && !replyApi?.dispatchReplyWithBufferedBlockDispatcher) {
|
|
8861
|
+
logger.warn("core.channel.reply real-time dispatcher not available, skipping dispatch");
|
|
8757
8862
|
return;
|
|
8758
8863
|
}
|
|
8759
8864
|
const resolveAgentRoute = routingApi.resolveAgentRoute;
|
|
@@ -9039,30 +9144,18 @@ async function handleDingtalkMessage(params) {
|
|
|
9039
9144
|
};
|
|
9040
9145
|
const payloadMediaUrls = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
|
9041
9146
|
const rawText = payload.text ?? "";
|
|
9042
|
-
const
|
|
9147
|
+
const preparedReply = prepareDingtalkReplyContent({
|
|
9043
9148
|
text: rawText,
|
|
9044
9149
|
logger
|
|
9045
9150
|
});
|
|
9046
|
-
const
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
const mediaQueue = [];
|
|
9051
|
-
const seenMedia = /* @__PURE__ */ new Set();
|
|
9052
|
-
const addMedia = (value) => {
|
|
9053
|
-
const trimmed = value?.trim();
|
|
9054
|
-
if (!trimmed) return;
|
|
9055
|
-
if (seenMedia.has(trimmed)) return;
|
|
9056
|
-
seenMedia.add(trimmed);
|
|
9057
|
-
mediaQueue.push(trimmed);
|
|
9058
|
-
};
|
|
9059
|
-
for (const url of payloadMediaUrls) addMedia(url);
|
|
9060
|
-
for (const url of mediaFromLines) addMedia(url);
|
|
9061
|
-
for (const url of localMediaFromText) addMedia(url);
|
|
9151
|
+
const mediaQueue = dedupeMediaUrls([
|
|
9152
|
+
...payloadMediaUrls,
|
|
9153
|
+
...preparedReply.mediaUrls
|
|
9154
|
+
]);
|
|
9062
9155
|
const converted = textApi?.convertMarkdownTables?.(
|
|
9063
|
-
|
|
9156
|
+
preparedReply.text,
|
|
9064
9157
|
tableMode
|
|
9065
|
-
) ??
|
|
9158
|
+
) ?? preparedReply.text;
|
|
9066
9159
|
const hasText = converted.trim().length > 0;
|
|
9067
9160
|
if (hasText) {
|
|
9068
9161
|
const chunks = textApi?.chunkTextWithMode && typeof textChunkLimitResolved === "number" && textChunkLimitResolved > 0 ? textApi.chunkTextWithMode(converted, textChunkLimitResolved, chunkMode) : [converted];
|
|
@@ -9089,18 +9182,20 @@ async function handleDingtalkMessage(params) {
|
|
|
9089
9182
|
cfg,
|
|
9090
9183
|
route?.agentId
|
|
9091
9184
|
);
|
|
9092
|
-
const createDispatcherWithTyping = replyApi?.createReplyDispatcherWithTyping;
|
|
9093
|
-
const createDispatcher = replyApi?.createReplyDispatcher;
|
|
9094
9185
|
const dispatchReplyWithDispatcher = replyApi?.dispatchReplyWithDispatcher;
|
|
9095
|
-
|
|
9186
|
+
const dispatchReplyWithBufferedBlockDispatcher = replyApi?.dispatchReplyWithBufferedBlockDispatcher;
|
|
9187
|
+
const streamingReplyOptions = chatType === "direct" ? {
|
|
9188
|
+
disableBlockStreaming: false
|
|
9189
|
+
} : void 0;
|
|
9190
|
+
const runRealtimeDispatch = async (mode, dispatchFn) => {
|
|
9096
9191
|
logger.debug(
|
|
9097
|
-
`[dispatch]
|
|
9192
|
+
`[dispatch] ${mode}=${JSON.stringify({
|
|
9098
9193
|
sessionKey: route?.sessionKey,
|
|
9099
9194
|
...resolvedTargetMeta
|
|
9100
9195
|
})}`
|
|
9101
9196
|
);
|
|
9102
9197
|
const deliveryState = { delivered: false, skippedNonSilent: 0 };
|
|
9103
|
-
const
|
|
9198
|
+
const result = await dispatchFn({
|
|
9104
9199
|
ctx: finalCtx,
|
|
9105
9200
|
cfg,
|
|
9106
9201
|
dispatcherOptions: {
|
|
@@ -9122,7 +9217,8 @@ async function handleDingtalkMessage(params) {
|
|
|
9122
9217
|
onError: (err, info) => {
|
|
9123
9218
|
logger.error(`${info.kind} reply failed: ${String(err)}`);
|
|
9124
9219
|
}
|
|
9125
|
-
}
|
|
9220
|
+
},
|
|
9221
|
+
replyOptions: streamingReplyOptions
|
|
9126
9222
|
});
|
|
9127
9223
|
if (!deliveryState.delivered && deliveryState.skippedNonSilent > 0) {
|
|
9128
9224
|
await sendMessageDingtalk({
|
|
@@ -9133,63 +9229,21 @@ async function handleDingtalkMessage(params) {
|
|
|
9133
9229
|
});
|
|
9134
9230
|
longTaskNotice.markReplyDelivered();
|
|
9135
9231
|
}
|
|
9136
|
-
const
|
|
9137
|
-
const
|
|
9232
|
+
const counts = result?.counts;
|
|
9233
|
+
const queuedFinal = result?.queuedFinal;
|
|
9138
9234
|
logger.debug(
|
|
9139
|
-
`dispatch complete (queuedFinal=${typeof
|
|
9235
|
+
`dispatch complete (queuedFinal=${typeof queuedFinal === "boolean" ? queuedFinal : "unknown"}, replies=${counts?.final ?? 0})`
|
|
9140
9236
|
);
|
|
9141
|
-
return;
|
|
9142
|
-
}
|
|
9143
|
-
const dispatcherResult = createDispatcherWithTyping ? createDispatcherWithTyping({
|
|
9144
|
-
deliver: async (payload, info) => {
|
|
9145
|
-
await deliver(payload, info);
|
|
9146
|
-
},
|
|
9147
|
-
humanDelay,
|
|
9148
|
-
onError: (err, info) => {
|
|
9149
|
-
logger.error(`${info.kind} reply failed: ${String(err)}`);
|
|
9150
|
-
}
|
|
9151
|
-
}) : {
|
|
9152
|
-
dispatcher: createDispatcher?.({
|
|
9153
|
-
deliver: async (payload, info) => {
|
|
9154
|
-
await deliver(payload, info);
|
|
9155
|
-
},
|
|
9156
|
-
humanDelay,
|
|
9157
|
-
onError: (err, info) => {
|
|
9158
|
-
logger.error(`${info.kind} reply failed: ${String(err)}`);
|
|
9159
|
-
}
|
|
9160
|
-
}),
|
|
9161
|
-
replyOptions: {},
|
|
9162
|
-
markDispatchIdle: () => void 0
|
|
9163
9237
|
};
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
logger.debug("dispatcher not available, skipping dispatch");
|
|
9238
|
+
if (dispatchReplyWithDispatcher) {
|
|
9239
|
+
await runRealtimeDispatch("direct", dispatchReplyWithDispatcher);
|
|
9167
9240
|
return;
|
|
9168
9241
|
}
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
sessionKey: route?.sessionKey,
|
|
9172
|
-
...resolvedTargetMeta
|
|
9173
|
-
})}`
|
|
9174
|
-
);
|
|
9175
|
-
const dispatchReplyFromConfig = replyApi?.dispatchReplyFromConfig;
|
|
9176
|
-
if (!dispatchReplyFromConfig) {
|
|
9177
|
-
logger.debug("dispatchReplyFromConfig not available");
|
|
9242
|
+
if (dispatchReplyWithBufferedBlockDispatcher) {
|
|
9243
|
+
await runRealtimeDispatch("buffered", dispatchReplyWithBufferedBlockDispatcher);
|
|
9178
9244
|
return;
|
|
9179
9245
|
}
|
|
9180
|
-
|
|
9181
|
-
ctx: finalCtx,
|
|
9182
|
-
cfg,
|
|
9183
|
-
dispatcher,
|
|
9184
|
-
replyOptions: dispatcherResult?.replyOptions ?? {}
|
|
9185
|
-
});
|
|
9186
|
-
const markDispatchIdle = dispatcherResult?.markDispatchIdle;
|
|
9187
|
-
markDispatchIdle?.();
|
|
9188
|
-
const counts = result?.counts;
|
|
9189
|
-
const queuedFinal = result?.queuedFinal;
|
|
9190
|
-
logger.debug(
|
|
9191
|
-
`dispatch complete (queuedFinal=${typeof queuedFinal === "boolean" ? queuedFinal : "unknown"}, replies=${counts?.final ?? 0})`
|
|
9192
|
-
);
|
|
9246
|
+
logger.warn("no real-time reply dispatcher available after capability check");
|
|
9193
9247
|
} catch (err) {
|
|
9194
9248
|
longTaskNotice.dispose();
|
|
9195
9249
|
throw err;
|
|
@@ -10043,14 +10097,37 @@ function resolveDingtalkAccount(params) {
|
|
|
10043
10097
|
function canStoreDefaultAccountInAccounts(cfg) {
|
|
10044
10098
|
return Boolean(cfg.channels?.dingtalk?.accounts?.[DEFAULT_ACCOUNT_ID]);
|
|
10045
10099
|
}
|
|
10100
|
+
function asRecord(value) {
|
|
10101
|
+
return value && typeof value === "object" ? value : void 0;
|
|
10102
|
+
}
|
|
10103
|
+
function hasRealtimeReplyApi(reply) {
|
|
10104
|
+
return Boolean(
|
|
10105
|
+
reply?.dispatchReplyWithDispatcher || reply?.dispatchReplyWithBufferedBlockDispatcher
|
|
10106
|
+
);
|
|
10107
|
+
}
|
|
10046
10108
|
function resolveRuntimeCandidate(params) {
|
|
10047
|
-
const runtimeRecord = params.runtime
|
|
10048
|
-
const runtimeChannel = runtimeRecord?.channel
|
|
10049
|
-
const channelRuntime = params.channelRuntime
|
|
10050
|
-
|
|
10051
|
-
|
|
10109
|
+
const runtimeRecord = asRecord(params.runtime);
|
|
10110
|
+
const runtimeChannel = asRecord(runtimeRecord?.channel);
|
|
10111
|
+
const channelRuntime = asRecord(params.channelRuntime);
|
|
10112
|
+
if (!runtimeRecord && !channelRuntime) {
|
|
10113
|
+
return void 0;
|
|
10114
|
+
}
|
|
10115
|
+
if (!runtimeChannel && !channelRuntime) {
|
|
10052
10116
|
return runtimeRecord;
|
|
10053
10117
|
}
|
|
10118
|
+
const resolvedChannel = {
|
|
10119
|
+
...runtimeChannel ?? {},
|
|
10120
|
+
...channelRuntime ?? {}
|
|
10121
|
+
};
|
|
10122
|
+
for (const key of ["routing", "reply", "session", "text"]) {
|
|
10123
|
+
const mergedSection = {
|
|
10124
|
+
...asRecord(runtimeChannel?.[key]) ?? {},
|
|
10125
|
+
...asRecord(channelRuntime?.[key]) ?? {}
|
|
10126
|
+
};
|
|
10127
|
+
if (Object.keys(mergedSection).length > 0) {
|
|
10128
|
+
resolvedChannel[key] = mergedSection;
|
|
10129
|
+
}
|
|
10130
|
+
}
|
|
10054
10131
|
return {
|
|
10055
10132
|
...runtimeRecord ?? {},
|
|
10056
10133
|
channel: resolvedChannel
|
|
@@ -10378,7 +10455,7 @@ var dingtalkPlugin = {
|
|
|
10378
10455
|
channelRuntime: ctx.channelRuntime
|
|
10379
10456
|
});
|
|
10380
10457
|
const candidate = runtimeCandidate;
|
|
10381
|
-
if (candidate?.channel?.routing?.resolveAgentRoute && candidate.channel?.reply
|
|
10458
|
+
if (candidate?.channel?.routing?.resolveAgentRoute && hasRealtimeReplyApi(candidate.channel?.reply)) {
|
|
10382
10459
|
setDingtalkRuntime(runtimeCandidate);
|
|
10383
10460
|
}
|
|
10384
10461
|
return monitorDingtalkProvider({
|