@openclaw-china/dingtalk 2026.3.18 → 2026.3.20
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 +252 -155
- 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,89 @@ 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
|
+
let activeDeliveryMode;
|
|
6970
|
+
if (replyMode === "active") {
|
|
6971
|
+
activeDeliveryMode = await prompter.askSelect(
|
|
6972
|
+
"\u4E3B\u52A8\u53D1\u9001\u6A21\u5F0F\uFF08activeDeliveryMode\uFF09",
|
|
6973
|
+
[
|
|
6974
|
+
{ value: "split", label: "split\uFF08\u9010\u5757\u53D1\u9001\uFF0C\u63A8\u8350\uFF09" },
|
|
6975
|
+
{ value: "merged", label: "merged\uFF08\u5408\u5E76\u540E\u5355\u6B21\u53D1\u9001\uFF09" }
|
|
6976
|
+
],
|
|
6977
|
+
toTrimmedString2(existing.activeDeliveryMode) ?? "split"
|
|
6978
|
+
);
|
|
6979
|
+
}
|
|
6980
|
+
const renderMarkdown = await prompter.askConfirm(
|
|
6981
|
+
"\u542F\u7528 Markdown \u6E32\u67D3\uFF08\u63A8\u8350\u5F00\u542F\uFF09",
|
|
6982
|
+
toBoolean(existing.renderMarkdown, true)
|
|
6983
|
+
);
|
|
6984
|
+
const welcomeText = await prompter.askText({
|
|
6985
|
+
label: "\u6B22\u8FCE\u8BED\uFF08\u53EF\u9009\uFF09",
|
|
6986
|
+
defaultValue: toTrimmedString2(existing.welcomeText),
|
|
6987
|
+
required: false
|
|
6988
|
+
});
|
|
6989
|
+
return mergeChannelConfig(cfg, "wechat-mp", {
|
|
6990
|
+
webhookPath,
|
|
6991
|
+
appId,
|
|
6992
|
+
appSecret: appSecret || void 0,
|
|
6993
|
+
token,
|
|
6994
|
+
encodingAESKey: messageMode === "plain" ? void 0 : encodingAESKey,
|
|
6995
|
+
messageMode,
|
|
6996
|
+
replyMode,
|
|
6997
|
+
activeDeliveryMode,
|
|
6998
|
+
renderMarkdown,
|
|
6909
6999
|
welcomeText: welcomeText || void 0
|
|
6910
7000
|
});
|
|
6911
7001
|
}
|
|
@@ -6967,6 +7057,8 @@ async function configureSingleChannel(channel, prompter, cfg) {
|
|
|
6967
7057
|
return configureWecomApp(prompter, cfg);
|
|
6968
7058
|
case "wecom-kf":
|
|
6969
7059
|
return configureWecomKf(prompter, cfg);
|
|
7060
|
+
case "wechat-mp":
|
|
7061
|
+
return configureWechatMp(prompter, cfg);
|
|
6970
7062
|
case "qqbot":
|
|
6971
7063
|
return configureQQBot(prompter, cfg);
|
|
6972
7064
|
default:
|
|
@@ -7108,6 +7200,7 @@ var SUPPORTED_CHANNELS = [
|
|
|
7108
7200
|
"wecom",
|
|
7109
7201
|
"wecom-app",
|
|
7110
7202
|
"wecom-kf",
|
|
7203
|
+
"wechat-mp",
|
|
7111
7204
|
"qqbot"
|
|
7112
7205
|
];
|
|
7113
7206
|
var CHINA_INSTALL_HINT_SHOWN_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
@@ -8251,6 +8344,52 @@ ${list}`;
|
|
|
8251
8344
|
|
|
8252
8345
|
${prompt}` : content;
|
|
8253
8346
|
}
|
|
8347
|
+
function escapeRegExp(value) {
|
|
8348
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8349
|
+
}
|
|
8350
|
+
function finalizeReplyText(text) {
|
|
8351
|
+
return text.replace(/\n{3,}/g, "\n\n").trim();
|
|
8352
|
+
}
|
|
8353
|
+
function stripLocalMediaSyntaxFromText(text, mediaItems) {
|
|
8354
|
+
let result = text;
|
|
8355
|
+
for (const media of mediaItems) {
|
|
8356
|
+
const source = media.source?.trim();
|
|
8357
|
+
if (!source) continue;
|
|
8358
|
+
const escapedSource = escapeRegExp(source);
|
|
8359
|
+
const fileName = media.fileName ?? path.basename(media.localPath ?? source);
|
|
8360
|
+
if (media.type === "image") {
|
|
8361
|
+
const pattern = new RegExp(`\\[!\\[[^\\]]*\\]\\(${escapedSource}\\)\\]\\([^\\)]+\\)`, "g");
|
|
8362
|
+
result = result.replace(pattern, "");
|
|
8363
|
+
}
|
|
8364
|
+
if (media.sourceKind === "markdown") {
|
|
8365
|
+
if (media.type === "image") {
|
|
8366
|
+
const pattern = new RegExp(`!\\[[^\\]]*\\]\\(${escapedSource}\\)`, "g");
|
|
8367
|
+
result = result.replace(pattern, "");
|
|
8368
|
+
} else {
|
|
8369
|
+
const pattern = new RegExp(`\\[[^\\]]*\\]\\(${escapedSource}\\)`, "g");
|
|
8370
|
+
result = result.replace(pattern, `[\u6587\u4EF6: ${fileName}]`);
|
|
8371
|
+
}
|
|
8372
|
+
continue;
|
|
8373
|
+
}
|
|
8374
|
+
if (result.includes(source)) {
|
|
8375
|
+
const replacement = media.type === "image" ? "" : `[\u6587\u4EF6: ${fileName}]`;
|
|
8376
|
+
result = result.split(source).join(replacement);
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8379
|
+
return finalizeReplyText(result);
|
|
8380
|
+
}
|
|
8381
|
+
function dedupeMediaUrls(values) {
|
|
8382
|
+
const mediaQueue = [];
|
|
8383
|
+
const seenMedia = /* @__PURE__ */ new Set();
|
|
8384
|
+
for (const value of values) {
|
|
8385
|
+
const trimmed = value?.trim();
|
|
8386
|
+
if (!trimmed) continue;
|
|
8387
|
+
if (seenMedia.has(trimmed)) continue;
|
|
8388
|
+
seenMedia.add(trimmed);
|
|
8389
|
+
mediaQueue.push(trimmed);
|
|
8390
|
+
}
|
|
8391
|
+
return mediaQueue;
|
|
8392
|
+
}
|
|
8254
8393
|
function extractLocalMediaFromText(params) {
|
|
8255
8394
|
const { text, logger } = params;
|
|
8256
8395
|
const result = extractMediaFromText(text, {
|
|
@@ -8270,13 +8409,17 @@ function extractLocalMediaFromText(params) {
|
|
|
8270
8409
|
parseBarePaths: true,
|
|
8271
8410
|
parseMarkdownLinks: true
|
|
8272
8411
|
});
|
|
8273
|
-
const
|
|
8274
|
-
|
|
8412
|
+
const localMedia = result.all.filter((m) => m.isLocal && m.localPath);
|
|
8413
|
+
const mediaUrls = localMedia.map((m) => m.localPath);
|
|
8414
|
+
return {
|
|
8415
|
+
text: stripLocalMediaSyntaxFromText(text, localMedia),
|
|
8416
|
+
mediaUrls
|
|
8417
|
+
};
|
|
8275
8418
|
}
|
|
8276
8419
|
function extractMediaLinesFromText(params) {
|
|
8277
8420
|
const { text, logger } = params;
|
|
8278
8421
|
const result = extractMediaFromText(text, {
|
|
8279
|
-
removeFromText:
|
|
8422
|
+
removeFromText: true,
|
|
8280
8423
|
checkExists: true,
|
|
8281
8424
|
existsSync: (p) => {
|
|
8282
8425
|
const exists = fs3.existsSync(p);
|
|
@@ -8294,6 +8437,18 @@ function extractMediaLinesFromText(params) {
|
|
|
8294
8437
|
const mediaUrls = result.all.map((m) => m.isLocal ? m.localPath ?? m.source : m.source).filter((m) => typeof m === "string" && m.trim().length > 0);
|
|
8295
8438
|
return { text: result.text, mediaUrls };
|
|
8296
8439
|
}
|
|
8440
|
+
function prepareDingtalkReplyContent(params) {
|
|
8441
|
+
const { text, logger } = params;
|
|
8442
|
+
const mediaLineResult = extractMediaLinesFromText({ text, logger });
|
|
8443
|
+
const localMediaResult = extractLocalMediaFromText({
|
|
8444
|
+
text: mediaLineResult.text,
|
|
8445
|
+
logger
|
|
8446
|
+
});
|
|
8447
|
+
return {
|
|
8448
|
+
text: localMediaResult.text,
|
|
8449
|
+
mediaUrls: dedupeMediaUrls([...mediaLineResult.mediaUrls, ...localMediaResult.mediaUrls])
|
|
8450
|
+
};
|
|
8451
|
+
}
|
|
8297
8452
|
function resolveAudioRecognition(raw) {
|
|
8298
8453
|
if (raw.msgtype !== "audio") return void 0;
|
|
8299
8454
|
if (!raw.content) return void 0;
|
|
@@ -8313,12 +8468,12 @@ function resolveAudioRecognition(raw) {
|
|
|
8313
8468
|
function resolveGatewayAuthFromConfigFile(logger) {
|
|
8314
8469
|
try {
|
|
8315
8470
|
const fs5 = __require("fs");
|
|
8316
|
-
const
|
|
8471
|
+
const path6 = __require("path");
|
|
8317
8472
|
const os4 = __require("os");
|
|
8318
8473
|
const home = os4.homedir();
|
|
8319
8474
|
const candidates = [
|
|
8320
|
-
|
|
8321
|
-
|
|
8475
|
+
path6.join(home, ".openclaw", "openclaw.json"),
|
|
8476
|
+
path6.join(home, ".openclaw", "config.json")
|
|
8322
8477
|
];
|
|
8323
8478
|
for (const filePath of candidates) {
|
|
8324
8479
|
if (!fs5.existsSync(filePath)) continue;
|
|
@@ -8347,7 +8502,8 @@ function resolveGatewayRequestParams(runtime2, dingtalkCfg, logger) {
|
|
|
8347
8502
|
const gatewayUrl = typeof gateway?.url === "string" ? gateway.url : `http://127.0.0.1:${gatewayPort}/v1/chat/completions`;
|
|
8348
8503
|
const authToken = dingtalkCfg.gatewayToken ?? dingtalkCfg.gatewayPassword ?? gateway?.auth?.token ?? gateway?.authToken ?? gateway?.token ?? process.env.OPENCLAW_GATEWAY_TOKEN ?? process.env.OPENCLAW_GATEWAY_PASSWORD ?? resolveGatewayAuthFromConfigFile(logger);
|
|
8349
8504
|
const headers = {
|
|
8350
|
-
"Content-Type": "application/json"
|
|
8505
|
+
"Content-Type": "application/json",
|
|
8506
|
+
"x-openclaw-message-channel": "dingtalk"
|
|
8351
8507
|
};
|
|
8352
8508
|
if (typeof authToken === "string" && authToken.trim()) {
|
|
8353
8509
|
headers["Authorization"] = `Bearer ${authToken}`;
|
|
@@ -8370,12 +8526,14 @@ async function* streamFromGateway(params) {
|
|
|
8370
8526
|
logger.debug(`[gateway] streaming via ${gatewayUrl}, session=${sessionKey}`);
|
|
8371
8527
|
const response = await fetch(gatewayUrl, {
|
|
8372
8528
|
method: "POST",
|
|
8373
|
-
headers
|
|
8529
|
+
headers: {
|
|
8530
|
+
...headers,
|
|
8531
|
+
"x-openclaw-session-key": sessionKey
|
|
8532
|
+
},
|
|
8374
8533
|
body: JSON.stringify({
|
|
8375
8534
|
model: "default",
|
|
8376
8535
|
messages: [{ role: "user", content: userContent }],
|
|
8377
|
-
stream: true
|
|
8378
|
-
user: sessionKey
|
|
8536
|
+
stream: true
|
|
8379
8537
|
}),
|
|
8380
8538
|
signal: abortSignal
|
|
8381
8539
|
});
|
|
@@ -8532,35 +8690,21 @@ async function handleAICardStreaming(params) {
|
|
|
8532
8690
|
}
|
|
8533
8691
|
const now = Date.now();
|
|
8534
8692
|
if (!firstFrameSent || now - lastUpdateTime >= updateInterval) {
|
|
8535
|
-
|
|
8693
|
+
const previewText = prepareDingtalkReplyContent({ text: accumulated }).text;
|
|
8694
|
+
await streamAICard(card, previewText, false);
|
|
8536
8695
|
lastUpdateTime = now;
|
|
8537
8696
|
firstFrameSent = true;
|
|
8538
8697
|
}
|
|
8539
8698
|
}
|
|
8540
|
-
|
|
8541
|
-
logger.info(`AI Card streaming completed with ${accumulated.length} chars`);
|
|
8542
|
-
const { mediaUrls: mediaFromLines } = extractMediaLinesFromText({
|
|
8543
|
-
text: accumulated,
|
|
8544
|
-
logger
|
|
8545
|
-
});
|
|
8546
|
-
const { mediaUrls: localMediaFromText } = extractLocalMediaFromText({
|
|
8699
|
+
const preparedReply = prepareDingtalkReplyContent({
|
|
8547
8700
|
text: accumulated,
|
|
8548
8701
|
logger
|
|
8549
8702
|
});
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
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) {
|
|
8703
|
+
await finishAICard(card, preparedReply.text, (msg) => logger.debug(msg));
|
|
8704
|
+
logger.info(`AI Card streaming completed with ${preparedReply.text.length} chars`);
|
|
8705
|
+
if (preparedReply.mediaUrls.length > 0) {
|
|
8706
|
+
logger.debug(`[stream] sending ${preparedReply.mediaUrls.length} media attachments`);
|
|
8707
|
+
for (const mediaUrl of preparedReply.mediaUrls) {
|
|
8564
8708
|
try {
|
|
8565
8709
|
await sendMediaDingtalk({
|
|
8566
8710
|
cfg: dingtalkCfg,
|
|
@@ -8584,9 +8728,13 @@ async function handleAICardStreaming(params) {
|
|
|
8584
8728
|
}
|
|
8585
8729
|
try {
|
|
8586
8730
|
const fallbackText = accumulated.trim() ? accumulated : formatStreamingInterruptMessage(err);
|
|
8731
|
+
const preparedReply = prepareDingtalkReplyContent({
|
|
8732
|
+
text: fallbackText,
|
|
8733
|
+
logger
|
|
8734
|
+
});
|
|
8587
8735
|
const limit = dingtalkCfg.textChunkLimit ?? 4e3;
|
|
8588
|
-
for (let i = 0; i <
|
|
8589
|
-
const chunk =
|
|
8736
|
+
for (let i = 0; i < preparedReply.text.length; i += limit) {
|
|
8737
|
+
const chunk = preparedReply.text.slice(i, i + limit);
|
|
8590
8738
|
await sendMessageDingtalk({
|
|
8591
8739
|
cfg: dingtalkCfg,
|
|
8592
8740
|
to: targetId,
|
|
@@ -8594,26 +8742,7 @@ async function handleAICardStreaming(params) {
|
|
|
8594
8742
|
chatType
|
|
8595
8743
|
});
|
|
8596
8744
|
}
|
|
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) {
|
|
8745
|
+
for (const mediaUrl of preparedReply.mediaUrls) {
|
|
8617
8746
|
await sendMediaDingtalk({
|
|
8618
8747
|
cfg: dingtalkCfg,
|
|
8619
8748
|
to: targetId,
|
|
@@ -8748,12 +8877,8 @@ async function handleDingtalkMessage(params) {
|
|
|
8748
8877
|
logger.debug("core.channel.routing.resolveAgentRoute not available, skipping dispatch");
|
|
8749
8878
|
return;
|
|
8750
8879
|
}
|
|
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");
|
|
8880
|
+
if (!replyApi?.dispatchReplyWithDispatcher && !replyApi?.dispatchReplyWithBufferedBlockDispatcher) {
|
|
8881
|
+
logger.warn("core.channel.reply real-time dispatcher not available, skipping dispatch");
|
|
8757
8882
|
return;
|
|
8758
8883
|
}
|
|
8759
8884
|
const resolveAgentRoute = routingApi.resolveAgentRoute;
|
|
@@ -9039,30 +9164,18 @@ async function handleDingtalkMessage(params) {
|
|
|
9039
9164
|
};
|
|
9040
9165
|
const payloadMediaUrls = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
|
9041
9166
|
const rawText = payload.text ?? "";
|
|
9042
|
-
const
|
|
9167
|
+
const preparedReply = prepareDingtalkReplyContent({
|
|
9043
9168
|
text: rawText,
|
|
9044
9169
|
logger
|
|
9045
9170
|
});
|
|
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);
|
|
9171
|
+
const mediaQueue = dedupeMediaUrls([
|
|
9172
|
+
...payloadMediaUrls,
|
|
9173
|
+
...preparedReply.mediaUrls
|
|
9174
|
+
]);
|
|
9062
9175
|
const converted = textApi?.convertMarkdownTables?.(
|
|
9063
|
-
|
|
9176
|
+
preparedReply.text,
|
|
9064
9177
|
tableMode
|
|
9065
|
-
) ??
|
|
9178
|
+
) ?? preparedReply.text;
|
|
9066
9179
|
const hasText = converted.trim().length > 0;
|
|
9067
9180
|
if (hasText) {
|
|
9068
9181
|
const chunks = textApi?.chunkTextWithMode && typeof textChunkLimitResolved === "number" && textChunkLimitResolved > 0 ? textApi.chunkTextWithMode(converted, textChunkLimitResolved, chunkMode) : [converted];
|
|
@@ -9089,18 +9202,20 @@ async function handleDingtalkMessage(params) {
|
|
|
9089
9202
|
cfg,
|
|
9090
9203
|
route?.agentId
|
|
9091
9204
|
);
|
|
9092
|
-
const createDispatcherWithTyping = replyApi?.createReplyDispatcherWithTyping;
|
|
9093
|
-
const createDispatcher = replyApi?.createReplyDispatcher;
|
|
9094
9205
|
const dispatchReplyWithDispatcher = replyApi?.dispatchReplyWithDispatcher;
|
|
9095
|
-
|
|
9206
|
+
const dispatchReplyWithBufferedBlockDispatcher = replyApi?.dispatchReplyWithBufferedBlockDispatcher;
|
|
9207
|
+
const streamingReplyOptions = chatType === "direct" ? {
|
|
9208
|
+
disableBlockStreaming: false
|
|
9209
|
+
} : void 0;
|
|
9210
|
+
const runRealtimeDispatch = async (mode, dispatchFn) => {
|
|
9096
9211
|
logger.debug(
|
|
9097
|
-
`[dispatch]
|
|
9212
|
+
`[dispatch] ${mode}=${JSON.stringify({
|
|
9098
9213
|
sessionKey: route?.sessionKey,
|
|
9099
9214
|
...resolvedTargetMeta
|
|
9100
9215
|
})}`
|
|
9101
9216
|
);
|
|
9102
9217
|
const deliveryState = { delivered: false, skippedNonSilent: 0 };
|
|
9103
|
-
const
|
|
9218
|
+
const result = await dispatchFn({
|
|
9104
9219
|
ctx: finalCtx,
|
|
9105
9220
|
cfg,
|
|
9106
9221
|
dispatcherOptions: {
|
|
@@ -9122,7 +9237,8 @@ async function handleDingtalkMessage(params) {
|
|
|
9122
9237
|
onError: (err, info) => {
|
|
9123
9238
|
logger.error(`${info.kind} reply failed: ${String(err)}`);
|
|
9124
9239
|
}
|
|
9125
|
-
}
|
|
9240
|
+
},
|
|
9241
|
+
replyOptions: streamingReplyOptions
|
|
9126
9242
|
});
|
|
9127
9243
|
if (!deliveryState.delivered && deliveryState.skippedNonSilent > 0) {
|
|
9128
9244
|
await sendMessageDingtalk({
|
|
@@ -9133,63 +9249,21 @@ async function handleDingtalkMessage(params) {
|
|
|
9133
9249
|
});
|
|
9134
9250
|
longTaskNotice.markReplyDelivered();
|
|
9135
9251
|
}
|
|
9136
|
-
const
|
|
9137
|
-
const
|
|
9252
|
+
const counts = result?.counts;
|
|
9253
|
+
const queuedFinal = result?.queuedFinal;
|
|
9138
9254
|
logger.debug(
|
|
9139
|
-
`dispatch complete (queuedFinal=${typeof
|
|
9255
|
+
`dispatch complete (queuedFinal=${typeof queuedFinal === "boolean" ? queuedFinal : "unknown"}, replies=${counts?.final ?? 0})`
|
|
9140
9256
|
);
|
|
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
9257
|
};
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
logger.debug("dispatcher not available, skipping dispatch");
|
|
9258
|
+
if (dispatchReplyWithDispatcher) {
|
|
9259
|
+
await runRealtimeDispatch("direct", dispatchReplyWithDispatcher);
|
|
9167
9260
|
return;
|
|
9168
9261
|
}
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
sessionKey: route?.sessionKey,
|
|
9172
|
-
...resolvedTargetMeta
|
|
9173
|
-
})}`
|
|
9174
|
-
);
|
|
9175
|
-
const dispatchReplyFromConfig = replyApi?.dispatchReplyFromConfig;
|
|
9176
|
-
if (!dispatchReplyFromConfig) {
|
|
9177
|
-
logger.debug("dispatchReplyFromConfig not available");
|
|
9262
|
+
if (dispatchReplyWithBufferedBlockDispatcher) {
|
|
9263
|
+
await runRealtimeDispatch("buffered", dispatchReplyWithBufferedBlockDispatcher);
|
|
9178
9264
|
return;
|
|
9179
9265
|
}
|
|
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
|
-
);
|
|
9266
|
+
logger.warn("no real-time reply dispatcher available after capability check");
|
|
9193
9267
|
} catch (err) {
|
|
9194
9268
|
longTaskNotice.dispose();
|
|
9195
9269
|
throw err;
|
|
@@ -10043,14 +10117,37 @@ function resolveDingtalkAccount(params) {
|
|
|
10043
10117
|
function canStoreDefaultAccountInAccounts(cfg) {
|
|
10044
10118
|
return Boolean(cfg.channels?.dingtalk?.accounts?.[DEFAULT_ACCOUNT_ID]);
|
|
10045
10119
|
}
|
|
10120
|
+
function asRecord(value) {
|
|
10121
|
+
return value && typeof value === "object" ? value : void 0;
|
|
10122
|
+
}
|
|
10123
|
+
function hasRealtimeReplyApi(reply) {
|
|
10124
|
+
return Boolean(
|
|
10125
|
+
reply?.dispatchReplyWithDispatcher || reply?.dispatchReplyWithBufferedBlockDispatcher
|
|
10126
|
+
);
|
|
10127
|
+
}
|
|
10046
10128
|
function resolveRuntimeCandidate(params) {
|
|
10047
|
-
const runtimeRecord = params.runtime
|
|
10048
|
-
const runtimeChannel = runtimeRecord?.channel
|
|
10049
|
-
const channelRuntime = params.channelRuntime
|
|
10050
|
-
|
|
10051
|
-
|
|
10129
|
+
const runtimeRecord = asRecord(params.runtime);
|
|
10130
|
+
const runtimeChannel = asRecord(runtimeRecord?.channel);
|
|
10131
|
+
const channelRuntime = asRecord(params.channelRuntime);
|
|
10132
|
+
if (!runtimeRecord && !channelRuntime) {
|
|
10133
|
+
return void 0;
|
|
10134
|
+
}
|
|
10135
|
+
if (!runtimeChannel && !channelRuntime) {
|
|
10052
10136
|
return runtimeRecord;
|
|
10053
10137
|
}
|
|
10138
|
+
const resolvedChannel = {
|
|
10139
|
+
...runtimeChannel ?? {},
|
|
10140
|
+
...channelRuntime ?? {}
|
|
10141
|
+
};
|
|
10142
|
+
for (const key of ["routing", "reply", "session", "text"]) {
|
|
10143
|
+
const mergedSection = {
|
|
10144
|
+
...asRecord(runtimeChannel?.[key]) ?? {},
|
|
10145
|
+
...asRecord(channelRuntime?.[key]) ?? {}
|
|
10146
|
+
};
|
|
10147
|
+
if (Object.keys(mergedSection).length > 0) {
|
|
10148
|
+
resolvedChannel[key] = mergedSection;
|
|
10149
|
+
}
|
|
10150
|
+
}
|
|
10054
10151
|
return {
|
|
10055
10152
|
...runtimeRecord ?? {},
|
|
10056
10153
|
channel: resolvedChannel
|
|
@@ -10378,7 +10475,7 @@ var dingtalkPlugin = {
|
|
|
10378
10475
|
channelRuntime: ctx.channelRuntime
|
|
10379
10476
|
});
|
|
10380
10477
|
const candidate = runtimeCandidate;
|
|
10381
|
-
if (candidate?.channel?.routing?.resolveAgentRoute && candidate.channel?.reply
|
|
10478
|
+
if (candidate?.channel?.routing?.resolveAgentRoute && hasRealtimeReplyApi(candidate.channel?.reply)) {
|
|
10382
10479
|
setDingtalkRuntime(runtimeCandidate);
|
|
10383
10480
|
}
|
|
10384
10481
|
return monitorDingtalkProvider({
|