@xmanrui/dsh-im 2.2.1 → 2.4.0
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/README.en.md +12 -3
- package/README.md +12 -3
- package/lib/client.js +95 -52
- package/lib/index.js +220 -191
- package/package.json +1 -1
- package/plugin-src/client/channels/feishu/index.js +37 -27
- package/plugin-src/client/channels/feishu/styles.js +26 -1
- package/plugin-src/client/i18n.js +27 -25
- package/src/channels/dingtalk/dingtalk-bridge.mjs +96 -3
- package/src/channels/discord/discord-runtime.mjs +3 -0
- package/src/channels/feishu/bridge.mjs +176 -31
- package/src/channels/feishu/feishu-cards.mjs +18 -6
- package/src/channels/feishu/feishu-runtime.mjs +0 -1
- package/src/channels/feishu/message-utils.mjs +1 -1
- package/src/channels/feishu/multi-bot-controller.mjs +1 -17
- package/src/channels/feishu/repair-manager.mjs +7 -2
- package/src/channels/qq/qq-bridge.mjs +99 -8
- package/src/channels/shared/batch-input.mjs +209 -0
- package/src/channels/shared/control-command.mjs +11 -1
- package/src/channels/shared/i18n-en/feishu.mjs +28 -25
- package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-b.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +49 -0
- package/src/channels/shared/i18n-en/telegram.mjs +1 -0
- package/src/channels/shared/text-harness-bridge.mjs +112 -2
- package/src/channels/slack/slack-runtime.mjs +1 -0
- package/src/channels/telegram/telegram-runtime.mjs +5 -0
- package/src/channels/wecom/wecom-bridge.mjs +102 -4
- package/src/channels/weixin/weixin-bridge.mjs +101 -3
- package/src/channels/weixin/weixin-runtime.mjs +18 -1
- package/src/channels/whatsapp/whatsapp-runtime.mjs +2 -0
package/package.json
CHANGED
|
@@ -265,7 +265,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
|
|
265
265
|
? h("img", {
|
|
266
266
|
src: qrSource,
|
|
267
267
|
alt: repairing
|
|
268
|
-
?
|
|
268
|
+
? `用于为${botName}补全权限与回调的一次性授权二维码`
|
|
269
269
|
: grantingGroupMessages
|
|
270
270
|
? `用于为${botName}开通群消息权限的一次性授权二维码`
|
|
271
271
|
: "用于新增 DeepSeek Harness 飞书机器人的一次性授权二维码",
|
|
@@ -293,31 +293,31 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
|
|
293
293
|
h("div", { className: "bxf-stateLabel dim-stateLabel" },
|
|
294
294
|
h("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }),
|
|
295
295
|
h("span", null, repairing
|
|
296
|
-
?
|
|
296
|
+
? `正在为「${botName}」补全权限与回调`
|
|
297
297
|
: grantingGroupMessages
|
|
298
298
|
? `正在为「${botName}」开通群消息权限`
|
|
299
299
|
: "正在添加新机器人")),
|
|
300
300
|
h("h3", null, expired
|
|
301
301
|
? "刷新二维码后继续"
|
|
302
302
|
: repairing
|
|
303
|
-
? "
|
|
303
|
+
? "使用飞书扫码补全权限"
|
|
304
304
|
: grantingGroupMessages
|
|
305
305
|
? "使用飞书确认群消息权限"
|
|
306
306
|
: "使用飞书扫码创建机器人"),
|
|
307
307
|
h("p", null, repairing
|
|
308
|
-
? "
|
|
308
|
+
? "扫码会更新现有飞书应用,最多增量补充卡片按钮回调、读取用户消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”),以及上传机器人图片或文件所需的 im:resource;不会创建新应用。确认页只显示当前缺少项,完成后此机器人会短暂重连,其他机器人不受影响。"
|
|
309
309
|
: grantingGroupMessages
|
|
310
310
|
? "扫码会更新现有飞书应用,只增量开通“获取群组中所有消息”权限;不会创建新应用。确认后会自动启用“响应所有群消息”,其他机器人不受影响。"
|
|
311
311
|
: "扫码只会新增一个机器人,已接入的机器人会继续正常收发消息。"),
|
|
312
312
|
h("ol", { className: "bxf-steps dim-steps" },
|
|
313
313
|
h("li", null, "打开飞书移动端,使用扫一扫读取二维码"),
|
|
314
314
|
h("li", null, repairing
|
|
315
|
-
? "
|
|
315
|
+
? "核对现有应用名称,并确认只新增当前缺少的上述配置"
|
|
316
316
|
: grantingGroupMessages
|
|
317
317
|
? "核对现有应用,并确认“获取群组中所有消息”权限"
|
|
318
318
|
: "核对应用名称与权限范围,并确认创建"),
|
|
319
319
|
h("li", null, repairing
|
|
320
|
-
? "
|
|
320
|
+
? "保持本页打开,等待权限与回调补全完成"
|
|
321
321
|
: grantingGroupMessages
|
|
322
322
|
? "保持本页打开,等待权限生效并自动切换响应方式"
|
|
323
323
|
: "保持本页打开,等待新机器人的长连接就绪")),
|
|
@@ -336,7 +336,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
|
|
336
336
|
? h(Button, { onClick: onRefresh, disabled: busy }, "换一个二维码")
|
|
337
337
|
: null,
|
|
338
338
|
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
|
339
|
-
? "
|
|
339
|
+
? "取消补全"
|
|
340
340
|
: grantingGroupMessages ? "取消授权" : "取消添加")),
|
|
341
341
|
),
|
|
342
342
|
),
|
|
@@ -354,16 +354,16 @@ function ProvisionProgress({ phase, provision, onCancel, busy }) {
|
|
|
354
354
|
h("div", { className: "dim-spinner", "aria-hidden": "true" }),
|
|
355
355
|
h("h3", null, connecting
|
|
356
356
|
? repairing
|
|
357
|
-
? "
|
|
357
|
+
? "已确认,正在完成权限与回调配置"
|
|
358
358
|
: grantingGroupMessages
|
|
359
359
|
? "已确认,正在启用全部消息模式"
|
|
360
360
|
: "已确认,正在连接新机器人"
|
|
361
361
|
: repairing
|
|
362
|
-
? "
|
|
362
|
+
? "正在准备权限补全二维码"
|
|
363
363
|
: grantingGroupMessages ? "正在准备权限授权二维码" : "正在准备授权二维码"),
|
|
364
364
|
h("p", null, connecting
|
|
365
365
|
? repairing
|
|
366
|
-
? "
|
|
366
|
+
? "配置已提交,正在保存权限、验证卡片回调并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
|
367
367
|
: grantingGroupMessages
|
|
368
368
|
? "权限配置已提交,正在保存设置并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
|
369
369
|
: "正在安全保存凭据并检查新机器人的消息通道,其他机器人不会中断。"
|
|
@@ -375,7 +375,7 @@ function ProvisionProgress({ phase, provision, onCancel, busy }) {
|
|
|
375
375
|
connecting && onCancel
|
|
376
376
|
? h("div", { className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } },
|
|
377
377
|
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
|
378
|
-
? "
|
|
378
|
+
? "取消补全"
|
|
379
379
|
: grantingGroupMessages ? "取消授权" : "取消添加"))
|
|
380
380
|
: null,
|
|
381
381
|
);
|
|
@@ -388,7 +388,7 @@ function ProvisionError({ error, provision, onRetry, onCancel, busy }) {
|
|
|
388
388
|
h("div", { className: "bxf-inlineError dim-inlineError", role: "alert" },
|
|
389
389
|
h("div", null,
|
|
390
390
|
h("h3", null, repairing
|
|
391
|
-
? "
|
|
391
|
+
? "权限与回调没有补全完成"
|
|
392
392
|
: grantingGroupMessages ? "群消息权限没有开通完成" : "新机器人没有添加完成"),
|
|
393
393
|
h("p", null, error.message),
|
|
394
394
|
error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null,
|
|
@@ -564,6 +564,7 @@ export function BotCard({
|
|
|
564
564
|
removeButtonRef,
|
|
565
565
|
}) {
|
|
566
566
|
const { bot, health, state, connected } = connection;
|
|
567
|
+
const repairTooltipId = React.useId();
|
|
567
568
|
const stateForDisplay = busy === "reconnect"
|
|
568
569
|
? "connecting"
|
|
569
570
|
: state;
|
|
@@ -637,13 +638,22 @@ export function BotCard({
|
|
|
637
638
|
disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : undefined,
|
|
638
639
|
"aria-label": `${connected ? "检查连接" : "重试连接"}${bot.name}`,
|
|
639
640
|
}, busy === "reconnect" ? (connected ? "检查中…" : "正在连接…") : connected ? "检查连接" : "重试连接"),
|
|
640
|
-
h(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
641
|
+
h("span", { className: "bxf-repairAction" },
|
|
642
|
+
h(Button, {
|
|
643
|
+
className: "bxf-repairButton dim-cardAction",
|
|
644
|
+
onClick: onRepairCallback,
|
|
645
|
+
disabled: Boolean(busy) || repairDisabled,
|
|
646
|
+
"aria-busy": busy === "callback-repair" ? "true" : undefined,
|
|
647
|
+
"aria-label": `为${bot.name}补全权限与回调`,
|
|
648
|
+
"aria-describedby": repairTooltipId,
|
|
649
|
+
}, busy === "callback-repair" ? "等待扫码…" : "补全权限"),
|
|
650
|
+
h("span", {
|
|
651
|
+
id: repairTooltipId,
|
|
652
|
+
className: "bxf-repairTooltip",
|
|
653
|
+
role: "tooltip",
|
|
654
|
+
},
|
|
655
|
+
h("strong", null, "补全范围"),
|
|
656
|
+
h("span", null, "最多增量添加卡片回调 card.action.trigger、读取消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”),以及上传机器人图片或文件所需的 im:resource;确认页只显示当前缺少项,不会创建新应用。"))),
|
|
647
657
|
h(Button, {
|
|
648
658
|
className: "dim-cardAction", kind: "danger", onClick: onRequestRemove,
|
|
649
659
|
disabled: Boolean(busy), ref: removeButtonRef,
|
|
@@ -933,7 +943,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
933
943
|
&& (provision.operation !== operation || provision.botId !== botId)) {
|
|
934
944
|
throw new Error(grantingGroupMessages
|
|
935
945
|
? "飞书服务返回了不匹配的群消息权限二维码"
|
|
936
|
-
: "
|
|
946
|
+
: "飞书服务返回了不匹配的权限补全二维码");
|
|
937
947
|
}
|
|
938
948
|
const timestamp = Date.now();
|
|
939
949
|
setNow(timestamp);
|
|
@@ -948,7 +958,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
948
958
|
},
|
|
949
959
|
}));
|
|
950
960
|
announce(repairing
|
|
951
|
-
? `${botName ?? "机器人"}
|
|
961
|
+
? `${botName ?? "机器人"}的权限补全二维码已生成,请使用飞书扫码。`
|
|
952
962
|
: grantingGroupMessages
|
|
953
963
|
? `${botName ?? "机器人"}的群消息权限二维码已生成,请使用飞书确认。`
|
|
954
964
|
: "授权二维码已生成,请使用飞书扫码。");
|
|
@@ -1032,7 +1042,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1032
1042
|
: current);
|
|
1033
1043
|
announce(grantingGroupMessages
|
|
1034
1044
|
? "权限配置已提交,正在启用全部消息模式并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
|
1035
|
-
: "
|
|
1045
|
+
: "配置已提交,正在保存权限、验证卡片回调并重连此机器人;此阶段无法取消,其他机器人不会中断。");
|
|
1036
1046
|
return;
|
|
1037
1047
|
}
|
|
1038
1048
|
if (result.status === "connected") {
|
|
@@ -1040,7 +1050,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1040
1050
|
setModel((current) => ({ ...current, provisioning: null }));
|
|
1041
1051
|
announce(grantingGroupMessages
|
|
1042
1052
|
? `${targetBotName}已开通群消息权限,并启用“响应所有群消息”。`
|
|
1043
|
-
: `${targetBotName}
|
|
1053
|
+
: `${targetBotName}的权限与回调已补全。`);
|
|
1044
1054
|
if (activeProvision.botId) setFocusBotId(activeProvision.botId);
|
|
1045
1055
|
await loadStatus({ silent: true, restoreProvisioning: false });
|
|
1046
1056
|
return;
|
|
@@ -1048,7 +1058,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1048
1058
|
}
|
|
1049
1059
|
setModel((current) => ({ ...current, provisioning: null }));
|
|
1050
1060
|
announce(repairing
|
|
1051
|
-
? "
|
|
1061
|
+
? "已取消补全权限与回调。"
|
|
1052
1062
|
: grantingGroupMessages ? "已取消群消息权限授权。" : "已取消添加机器人。");
|
|
1053
1063
|
await loadStatus({ silent: true, restoreProvisioning: false });
|
|
1054
1064
|
scheduleAnimationFrame(() => {
|
|
@@ -1117,7 +1127,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1117
1127
|
const targetBot = snapshot?.bots.find((bot) => bot.botId === result.botId);
|
|
1118
1128
|
if (!snapshot) {
|
|
1119
1129
|
throw new Error(isCallbackRepair(provision)
|
|
1120
|
-
? "
|
|
1130
|
+
? "权限与回调已更新,但暂时无法确认机器人连接状态"
|
|
1121
1131
|
: isGroupMessagePermission(provision)
|
|
1122
1132
|
? "群消息权限已更新,但暂时无法确认机器人连接状态"
|
|
1123
1133
|
: "机器人已经创建,但暂时无法确认连接状态");
|
|
@@ -1130,7 +1140,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1130
1140
|
}
|
|
1131
1141
|
setModel((current) => ({ ...current, provisioning: null }));
|
|
1132
1142
|
announce(isCallbackRepair(provision)
|
|
1133
|
-
? `${targetBot.bot.name}
|
|
1143
|
+
? `${targetBot.bot.name}的权限与回调已补全。`
|
|
1134
1144
|
: isGroupMessagePermission(provision)
|
|
1135
1145
|
? `${targetBot.bot.name}已开通群消息权限,并启用“响应所有群消息”。`
|
|
1136
1146
|
: targetBot
|
|
@@ -1142,7 +1152,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1142
1152
|
if (result.status === "failed") {
|
|
1143
1153
|
const error = new Error(result.message
|
|
1144
1154
|
?? (isCallbackRepair(provision)
|
|
1145
|
-
? "
|
|
1155
|
+
? "飞书权限与回调补全失败"
|
|
1146
1156
|
: isGroupMessagePermission(provision)
|
|
1147
1157
|
? "飞书群消息权限开通失败"
|
|
1148
1158
|
: "飞书应用创建失败"));
|
|
@@ -450,10 +450,35 @@ const CSS = String.raw`
|
|
|
450
450
|
.bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
|
451
451
|
.bxf-healthSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; }
|
|
452
452
|
.bxf-healthSummary[data-error="true"] { color: var(--bxf-error); }
|
|
453
|
-
.bxf-botActions { flex: none; width: 100%; flex-wrap: wrap; gap: 8px; margin-top: 0; justify-content: flex-end; }
|
|
453
|
+
.bxf-botActions { position: relative; flex: none; width: 100%; flex-wrap: wrap; gap: 8px; margin-top: 0; justify-content: flex-end; }
|
|
454
454
|
.bxf-botActions .bxf-button { flex: none; white-space: nowrap; }
|
|
455
455
|
.bxf-botActions .bxf-repairButton { color: var(--bxf-accent); border-color: color-mix(in srgb, var(--bxf-accent) 35%, var(--dsw-alias-border-l2, #dee0e3)); }
|
|
456
456
|
.bxf-botActions .bxf-repairButton:hover:not(:disabled) { background: color-mix(in srgb, var(--bxf-accent) 7%, transparent); }
|
|
457
|
+
.bxf-repairAction { display: inline-flex; }
|
|
458
|
+
.bxf-repairTooltip {
|
|
459
|
+
position: absolute;
|
|
460
|
+
right: 0;
|
|
461
|
+
bottom: calc(100% + 8px);
|
|
462
|
+
z-index: 40;
|
|
463
|
+
width: min(330px, 100%);
|
|
464
|
+
display: grid;
|
|
465
|
+
gap: 3px;
|
|
466
|
+
padding: 9px 10px;
|
|
467
|
+
border: 1px solid var(--dsw-alias-border-l2, #dfe1e5);
|
|
468
|
+
border-radius: 8px;
|
|
469
|
+
color: var(--dsw-alias-label-primary, #1f2329);
|
|
470
|
+
background: var(--dsw-alias-bg-layer-3, #fff);
|
|
471
|
+
box-shadow: 0 10px 28px rgb(31 35 41 / 16%);
|
|
472
|
+
opacity: 0;
|
|
473
|
+
visibility: hidden;
|
|
474
|
+
transform: translateY(3px);
|
|
475
|
+
pointer-events: none;
|
|
476
|
+
transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
|
|
477
|
+
}
|
|
478
|
+
.bxf-repairTooltip strong { font-size: 12px; line-height: 17px; font-weight: 650; }
|
|
479
|
+
.bxf-repairTooltip > span { color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 17px; font-weight: 400; overflow-wrap: anywhere; }
|
|
480
|
+
.bxf-repairAction:hover .bxf-repairTooltip,
|
|
481
|
+
.bxf-repairAction:focus-within .bxf-repairTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
|
|
457
482
|
|
|
458
483
|
.bxf-confirm {
|
|
459
484
|
border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3);
|
|
@@ -231,7 +231,7 @@ const EN = Object.freeze({
|
|
|
231
231
|
'保持本页打开,等待新机器人的长连接就绪': 'Keep this page open until the bot connection is ready',
|
|
232
232
|
'在飞书中打开': 'Open in Feishu',
|
|
233
233
|
'取消添加': 'Cancel',
|
|
234
|
-
'
|
|
234
|
+
'使用飞书扫码补全权限': 'Scan with Feishu to complete permissions',
|
|
235
235
|
'使用飞书确认群消息权限': 'Confirm group-message permission with Feishu',
|
|
236
236
|
'扫码会更新现有飞书应用,只增量开通“获取群组中所有消息”权限;不会创建新应用。确认后会自动启用“响应所有群消息”,其他机器人不受影响。': 'Scanning updates the existing Feishu app with only the “Read all messages in associated group chat” scope. It does not create a new app. After confirmation, “Respond to all group messages” is enabled automatically; other bots are unaffected.',
|
|
237
237
|
'核对现有应用,并确认“获取群组中所有消息”权限': 'Review the existing app and confirm the “Read all messages in associated group chat” permission',
|
|
@@ -242,18 +242,20 @@ const EN = Object.freeze({
|
|
|
242
242
|
'权限配置已提交,正在启用全部消息模式并重连此机器人;此阶段无法取消,其他机器人不会中断。': 'The permission update was submitted. Enabling all-message mode and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.',
|
|
243
243
|
'正在为现有飞书应用申请群消息权限二维码,请稍候。': 'Requesting a group-message permission QR code for the existing Feishu app…',
|
|
244
244
|
'群消息权限没有开通完成': 'Group-message permission was not granted',
|
|
245
|
-
'
|
|
246
|
-
'
|
|
247
|
-
'
|
|
248
|
-
'
|
|
249
|
-
'
|
|
250
|
-
'
|
|
251
|
-
'
|
|
245
|
+
'扫码会更新现有飞书应用,最多增量补充卡片按钮回调、读取用户消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”),以及上传机器人图片或文件所需的 im:resource;不会创建新应用。确认页只显示当前缺少项,完成后此机器人会短暂重连,其他机器人不受影响。': 'Scanning updates the existing Feishu app with up to three missing items: the card-button callback, im:message:readonly for reading images or files in user messages (shown by Feishu as “Read direct and group messages”), and im:resource for uploading images or files sent by the bot. It does not create a new app. The confirmation page shows only missing items; this bot reconnects briefly afterward, while other bots are unaffected.',
|
|
246
|
+
'核对现有应用名称,并确认只新增当前缺少的上述配置': 'Review the existing app name and confirm that only the missing items described above are added',
|
|
247
|
+
'保持本页打开,等待权限与回调补全完成': 'Keep this page open until permissions and the callback are complete',
|
|
248
|
+
'取消补全': 'Cancel setup',
|
|
249
|
+
'已确认,正在完成权限与回调配置': 'Confirmed. Completing permissions and callback setup',
|
|
250
|
+
'正在准备权限补全二维码': 'Preparing the permission-completion QR code',
|
|
251
|
+
'配置已提交,正在保存权限、验证卡片回调并重连此机器人;此阶段无法取消,其他机器人不会中断。': 'The update was submitted. Saving permissions, verifying the card callback, and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.',
|
|
252
252
|
'正在为现有飞书应用申请一次性更新二维码,请稍候。': 'Requesting a one-time update QR code for the existing Feishu app…',
|
|
253
|
-
'
|
|
254
|
-
'
|
|
253
|
+
'权限与回调没有补全完成': 'Permissions and callback setup did not finish',
|
|
254
|
+
'补全权限': 'Complete permissions',
|
|
255
|
+
'补全范围': 'Completion scope',
|
|
256
|
+
'最多增量添加卡片回调 card.action.trigger、读取消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”),以及上传机器人图片或文件所需的 im:resource;确认页只显示当前缺少项,不会创建新应用。': 'Adds up to the missing card callback card.action.trigger, im:message:readonly for reading images or files in messages (shown by Feishu as “Read direct and group messages”), and im:resource for uploading images or files sent by the bot. The confirmation page shows only missing items; no new app is created.',
|
|
255
257
|
'等待扫码…': 'Waiting for scan…',
|
|
256
|
-
'
|
|
258
|
+
'飞书服务返回了不匹配的权限补全二维码': 'Feishu returned a permission-completion QR code for a different bot',
|
|
257
259
|
'飞书服务返回了不匹配的群消息权限二维码': 'Feishu returned a group-message permission QR code for a different bot',
|
|
258
260
|
'飞书服务返回的修复信息缺少 botId': 'Feishu repair status is missing the bot ID',
|
|
259
261
|
'飞书服务返回的应用更新信息缺少 botId': 'Feishu app-update status is missing the bot ID',
|
|
@@ -261,16 +263,16 @@ const EN = Object.freeze({
|
|
|
261
263
|
'此机器人': 'this bot',
|
|
262
264
|
'用于为${botName}开通群消息权限的一次性授权二维码': 'One-time QR code for granting group-message permission to ${botName}',
|
|
263
265
|
'正在为「${botName}」开通群消息权限': 'Granting group-message permission to “${botName}”',
|
|
264
|
-
'${botName ?? "机器人"}
|
|
266
|
+
'${botName ?? "机器人"}的权限补全二维码已生成,请使用飞书扫码。': 'Permission-completion QR code generated for ${botName ?? "bot"}. Scan it with Feishu.',
|
|
265
267
|
'${botName ?? "机器人"}的群消息权限二维码已生成,请使用飞书确认。': 'Group-message permission QR code generated for ${botName ?? "bot"}. Confirm it with Feishu.',
|
|
266
268
|
'${targetBotName}已开通群消息权限,并启用“响应所有群消息”。': '${targetBotName} now has group-message permission and “Respond to all group messages” is enabled.',
|
|
267
269
|
'${targetBot.bot.name}已开通群消息权限,并启用“响应所有群消息”。': '${targetBot.bot.name} now has group-message permission and “Respond to all group messages” is enabled.',
|
|
268
270
|
'${targetBot.bot.name}已连接,可以在飞书中开始聊天。': '${targetBot.bot.name} is connected and ready to chat in Feishu.',
|
|
269
|
-
'
|
|
271
|
+
'已取消补全权限与回调。': 'Completing permissions and the callback was cancelled.',
|
|
270
272
|
'已取消群消息权限授权。': 'Group-message permission authorization was cancelled.',
|
|
271
|
-
'
|
|
273
|
+
'权限与回调已更新,但暂时无法确认机器人连接状态': 'Permissions and the callback were updated, but the bot connection could not be confirmed yet',
|
|
272
274
|
'群消息权限已更新,但暂时无法确认机器人连接状态': 'The group-message permission was updated, but the bot connection could not be confirmed yet',
|
|
273
|
-
'
|
|
275
|
+
'飞书权限与回调补全失败': 'Could not complete Feishu permissions and callback setup',
|
|
274
276
|
'飞书群消息权限开通失败': 'Could not grant the Feishu group-message permission',
|
|
275
277
|
'请先完成当前飞书授权操作,再开通群消息权限。': 'Finish the current Feishu authorization before granting group-message permission.',
|
|
276
278
|
'已确认,正在连接新机器人': 'Confirmed. Connecting the new bot',
|
|
@@ -598,22 +600,22 @@ function translateDynamic(text) {
|
|
|
598
600
|
if (match) return `Remove ${match[1]} from DeepSeek Harness`;
|
|
599
601
|
match = /^(.+)的飞书授权流程$/.exec(text);
|
|
600
602
|
if (match) return `Feishu authorization flow for ${match[1]}`;
|
|
601
|
-
match =
|
|
602
|
-
if (match) return `One-time QR code for
|
|
603
|
+
match = /^用于为(.+)补全权限与回调的一次性授权二维码$/.exec(text);
|
|
604
|
+
if (match) return `One-time QR code for completing permissions and the callback for ${match[1]}`;
|
|
603
605
|
match = /^用于为(.+)开通群消息权限的一次性授权二维码$/.exec(text);
|
|
604
606
|
if (match) return `One-time QR code for granting group-message permission to ${match[1]}`;
|
|
605
|
-
match =
|
|
606
|
-
if (match) return `
|
|
607
|
+
match = /^正在为「(.+)」补全权限与回调$/.exec(text);
|
|
608
|
+
if (match) return `Completing permissions and the callback for “${match[1]}”`;
|
|
607
609
|
match = /^正在为「(.+)」开通群消息权限$/.exec(text);
|
|
608
610
|
if (match) return `Granting group-message permission to “${match[1]}”`;
|
|
609
|
-
match =
|
|
610
|
-
if (match) return `
|
|
611
|
-
match = /^(.+)
|
|
612
|
-
if (match) return `
|
|
611
|
+
match = /^为(.+)补全权限与回调$/.exec(text);
|
|
612
|
+
if (match) return `Complete permissions and the callback for ${match[1]}`;
|
|
613
|
+
match = /^(.+)的权限补全二维码已生成,请使用飞书扫码。$/.exec(text);
|
|
614
|
+
if (match) return `Permission-completion QR code generated for ${match[1]}. Scan it with Feishu.`;
|
|
613
615
|
match = /^(.+)的群消息权限二维码已生成,请使用飞书确认。$/.exec(text);
|
|
614
616
|
if (match) return `Group-message permission QR code generated for ${match[1]}. Confirm it with Feishu.`;
|
|
615
|
-
match = /^(.+)
|
|
616
|
-
if (match) return `
|
|
617
|
+
match = /^(.+)的权限与回调已补全。$/.exec(text);
|
|
618
|
+
if (match) return `Permissions and the callback completed for ${match[1]}.`;
|
|
617
619
|
match = /^(.+)已开通群消息权限,并启用“响应所有群消息”。$/.exec(text);
|
|
618
620
|
if (match) return `${match[1]} now has group-message permission and “Respond to all group messages” is enabled.`;
|
|
619
621
|
match = /^(检查连接|重试连接)(.+)$/.exec(text);
|
|
@@ -24,6 +24,12 @@ import {
|
|
|
24
24
|
} from '../shared/preset-command.mjs';
|
|
25
25
|
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
26
26
|
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
27
|
+
import {
|
|
28
|
+
BatchInputManager,
|
|
29
|
+
batchInputBusyMessage,
|
|
30
|
+
batchInputGroupUnsupportedMessage,
|
|
31
|
+
isBatchInputCommand,
|
|
32
|
+
} from '../shared/batch-input.mjs';
|
|
27
33
|
import {
|
|
28
34
|
hasInboundImages,
|
|
29
35
|
imagePromptUserMessage,
|
|
@@ -67,7 +73,11 @@ const HELP_TEXT_LINES = [
|
|
|
67
73
|
'/preset --default 跟随 Host 默认',
|
|
68
74
|
'/stop 停止当前任务',
|
|
69
75
|
'/steer 补充指令 纠偏当前任务',
|
|
76
|
+
'/batch 开始批量输入(仅私聊,最多 10 条文字)',
|
|
77
|
+
'/send 提交当前批次',
|
|
78
|
+
'/cancel 取消当前批次',
|
|
70
79
|
'/status 检查连接状态',
|
|
80
|
+
'/version 查看插件版本',
|
|
71
81
|
'/help 显示本帮助',
|
|
72
82
|
];
|
|
73
83
|
|
|
@@ -344,6 +354,7 @@ export class DingtalkHarnessBridge {
|
|
|
344
354
|
#commandTasks = new Set();
|
|
345
355
|
#acceptedMessageIds = new Set();
|
|
346
356
|
#approvals;
|
|
357
|
+
#batchInputs = new BatchInputManager();
|
|
347
358
|
|
|
348
359
|
constructor({
|
|
349
360
|
api,
|
|
@@ -416,12 +427,52 @@ export class DingtalkHarnessBridge {
|
|
|
416
427
|
clientSecret: this.#clientSecret,
|
|
417
428
|
});
|
|
418
429
|
const commandText = nonEmptyString(promptMessage.content) ?? '';
|
|
430
|
+
const addressed = String(message.conversationType) !== '2' || message?.isInAtList === true;
|
|
431
|
+
const direct = String(message.conversationType) !== '2';
|
|
432
|
+
const batchCommand = String(message?.msgtype).toLowerCase() === 'text'
|
|
433
|
+
&& isBatchInputCommand(commandText);
|
|
434
|
+
const batchStatus = this.#batchInputs.status(key);
|
|
435
|
+
if (batchCommand && !direct && sessionWebhook && addressed) {
|
|
436
|
+
return this.#finishBatchResult(
|
|
437
|
+
messageId,
|
|
438
|
+
sessionWebhook,
|
|
439
|
+
{ message: batchInputGroupUnsupportedMessage() },
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
if (direct && sessionWebhook && (batchCommand || batchStatus.phase === 'collecting')) {
|
|
443
|
+
const exactBatchStart = /^\/batch$/iu.test(commandText);
|
|
444
|
+
const result = exactBatchStart
|
|
445
|
+
&& batchStatus.phase === 'idle'
|
|
446
|
+
&& (this.#queues.has(key) || pending || this.#approvals.hasPending(key))
|
|
447
|
+
? { handled: true, kind: 'busy', message: batchInputBusyMessage() }
|
|
448
|
+
: this.#batchInputs.handle(key, commandText, {
|
|
449
|
+
plainText: Boolean(commandText)
|
|
450
|
+
&& String(message?.msgtype).toLowerCase() === 'text'
|
|
451
|
+
&& !hasInboundFiles(promptMessage)
|
|
452
|
+
&& !hasInboundImages(promptMessage),
|
|
453
|
+
});
|
|
454
|
+
if (result.handled) {
|
|
455
|
+
if (result.kind === 'submit') {
|
|
456
|
+
return this.#enqueueMessage(
|
|
457
|
+
{
|
|
458
|
+
...message,
|
|
459
|
+
msgtype: 'text',
|
|
460
|
+
text: { content: result.prompt },
|
|
461
|
+
},
|
|
462
|
+
messageId,
|
|
463
|
+
sender,
|
|
464
|
+
key,
|
|
465
|
+
{ batchSubmission: result },
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
return this.#finishBatchResult(messageId, sessionWebhook, result);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
419
471
|
const commandRunner = hasInboundFiles(promptMessage) ? null : isControlCommand(commandText)
|
|
420
472
|
? runControlCommand
|
|
421
473
|
: (isModelCommand(commandText)
|
|
422
474
|
? runModelCommand
|
|
423
475
|
: (isPresetCommand(commandText) ? runPresetCommand : null));
|
|
424
|
-
const addressed = String(message.conversationType) !== '2' || message?.isInAtList === true;
|
|
425
476
|
if (commandRunner && sessionWebhook && addressed) {
|
|
426
477
|
let task;
|
|
427
478
|
task = this.#processFastCommand(
|
|
@@ -521,6 +572,7 @@ export class DingtalkHarnessBridge {
|
|
|
521
572
|
#enqueueMessage(message, messageId, sender, key, {
|
|
522
573
|
releaseMessageId = true,
|
|
523
574
|
alreadyRecorded = false,
|
|
575
|
+
batchSubmission = null,
|
|
524
576
|
} = {}) {
|
|
525
577
|
let hasSafeReplyRoute = false;
|
|
526
578
|
try {
|
|
@@ -543,6 +595,7 @@ export class DingtalkHarnessBridge {
|
|
|
543
595
|
.then(() => this.#process(message, messageId, sender, key, {
|
|
544
596
|
alreadyRecorded,
|
|
545
597
|
preparedMessage,
|
|
598
|
+
batchSubmission,
|
|
546
599
|
}))
|
|
547
600
|
.finally(() => {
|
|
548
601
|
if (releaseMessageId) this.#acceptedMessageIds.delete(messageId);
|
|
@@ -595,9 +648,32 @@ export class DingtalkHarnessBridge {
|
|
|
595
648
|
this.#status.lastError = null;
|
|
596
649
|
}
|
|
597
650
|
|
|
651
|
+
#finishBatchResult(messageId, sessionWebhook, result) {
|
|
652
|
+
let task;
|
|
653
|
+
task = Promise.resolve().then(async () => {
|
|
654
|
+
if (this.#state.hasSeen(messageId)) return;
|
|
655
|
+
await this.#state.markSeen(messageId);
|
|
656
|
+
increment(this.#status, 'messagesReceived');
|
|
657
|
+
this.#status.lastMessageAt = new Date().toISOString();
|
|
658
|
+
if (result.message) await this.#send(sessionWebhook, result.message);
|
|
659
|
+
this.#status.lastError = null;
|
|
660
|
+
}).catch(async (error) => {
|
|
661
|
+
if (this.#signal?.aborted) return;
|
|
662
|
+
this.#status.lastError = t('钉钉命令处理失败。');
|
|
663
|
+
this.#logger.error?.('[dsh-dingtalk] failed to process a batch input message', safeErrorDiagnostic(error));
|
|
664
|
+
await this.#send(sessionWebhook, t(CARD_ERROR_TEXT)).catch(() => undefined);
|
|
665
|
+
}).finally(() => {
|
|
666
|
+
this.#acceptedMessageIds.delete(messageId);
|
|
667
|
+
this.#commandTasks.delete(task);
|
|
668
|
+
});
|
|
669
|
+
this.#commandTasks.add(task);
|
|
670
|
+
return task;
|
|
671
|
+
}
|
|
672
|
+
|
|
598
673
|
async #process(message, messageId, sender, key, {
|
|
599
674
|
alreadyRecorded = false,
|
|
600
675
|
preparedMessage,
|
|
676
|
+
batchSubmission = null,
|
|
601
677
|
} = {}) {
|
|
602
678
|
this.#signal?.throwIfAborted();
|
|
603
679
|
if (!alreadyRecorded) {
|
|
@@ -633,6 +709,7 @@ export class DingtalkHarnessBridge {
|
|
|
633
709
|
const isPlainText = String(message?.msgtype).toLowerCase() === 'text';
|
|
634
710
|
let cardStream = null;
|
|
635
711
|
let cardStarted = false;
|
|
712
|
+
let batchSettled = batchSubmission === null;
|
|
636
713
|
try {
|
|
637
714
|
if (!text && !hasImages && !hasFiles) {
|
|
638
715
|
await this.#send(sessionWebhook, t('目前支持文字、图片和文件消息。'));
|
|
@@ -717,6 +794,10 @@ export class DingtalkHarnessBridge {
|
|
|
717
794
|
files: promptMessage.files,
|
|
718
795
|
},
|
|
719
796
|
});
|
|
797
|
+
if (batchSubmission) {
|
|
798
|
+
this.#batchInputs.complete(key, batchSubmission.token);
|
|
799
|
+
batchSettled = true;
|
|
800
|
+
}
|
|
720
801
|
const answerText = typeof answer === 'string' && answer.trim()
|
|
721
802
|
? answer
|
|
722
803
|
: artifacts.length > 0 ? t('结果文件已生成。') : answer;
|
|
@@ -753,6 +834,15 @@ export class DingtalkHarnessBridge {
|
|
|
753
834
|
this.#status.lastError = null;
|
|
754
835
|
return delivery.receipt;
|
|
755
836
|
} catch (error) {
|
|
837
|
+
let batchFailureMessage = null;
|
|
838
|
+
if (!batchSettled && batchSubmission) {
|
|
839
|
+
if (error?.code === 'turn-stopped') {
|
|
840
|
+
this.#batchInputs.complete(key, batchSubmission.token);
|
|
841
|
+
} else {
|
|
842
|
+
batchFailureMessage = this.#batchInputs.fail(key, batchSubmission.token).message ?? null;
|
|
843
|
+
}
|
|
844
|
+
batchSettled = true;
|
|
845
|
+
}
|
|
756
846
|
if (error?.code === 'turn-stopped') {
|
|
757
847
|
if (cardStarted) await cardStream.finish(t('已停止。')).catch(() => undefined);
|
|
758
848
|
return;
|
|
@@ -767,8 +857,11 @@ export class DingtalkHarnessBridge {
|
|
|
767
857
|
const errorText = inboundFileUserMessage(error)
|
|
768
858
|
?? dingtalkImageErrorUserMessage(error)
|
|
769
859
|
?? t(CARD_ERROR_TEXT);
|
|
770
|
-
const
|
|
771
|
-
|
|
860
|
+
const visibleError = batchFailureMessage
|
|
861
|
+
? `${errorText}\n\n${batchFailureMessage}`
|
|
862
|
+
: errorText;
|
|
863
|
+
const streamed = cardStarted && await cardStream.finish(visibleError);
|
|
864
|
+
if (!streamed) await this.#send(sessionWebhook, visibleError);
|
|
772
865
|
} catch {
|
|
773
866
|
this.#logger.error?.('[dsh-dingtalk] failed to send the safe error reply');
|
|
774
867
|
}
|
|
@@ -217,6 +217,9 @@ export function normalizeDiscordMessage(message, botId, { fetchImpl = fetch } =
|
|
|
217
217
|
kind: direct ? 'direct' : 'group',
|
|
218
218
|
conversationId: String(message.channel_id),
|
|
219
219
|
content: stripBotMention(message.content ?? '', botId),
|
|
220
|
+
plainText: (!Array.isArray(message.attachments) || message.attachments.length === 0)
|
|
221
|
+
&& (!Array.isArray(message.sticker_items) || message.sticker_items.length === 0)
|
|
222
|
+
&& !message.poll,
|
|
220
223
|
images: Array.isArray(message.attachments)
|
|
221
224
|
? message.attachments.map((attachment) => discordImageSource(attachment, fetchImpl)).filter(Boolean)
|
|
222
225
|
: [],
|