@xmanrui/dsh-im 2.3.0 → 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 +8 -3
- package/README.md +8 -3
- package/lib/client.js +95 -52
- package/lib/index.js +173 -172
- 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 +1 -0
- package/src/channels/feishu/bridge.mjs +49 -27
- package/src/channels/feishu/feishu-cards.mjs +10 -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 +1 -0
- package/src/channels/shared/control-command.mjs +11 -1
- package/src/channels/shared/i18n-en/feishu.mjs +27 -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/telegram.mjs +1 -0
- package/src/channels/shared/text-harness-bridge.mjs +1 -0
- package/src/channels/telegram/telegram-runtime.mjs +1 -0
- package/src/channels/wecom/wecom-bridge.mjs +1 -0
- package/src/channels/weixin/weixin-bridge.mjs +1 -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);
|
|
@@ -106,9 +106,10 @@ const CARD_OVERLOAD_NOTICE_COOLDOWN_MS = 5_000;
|
|
|
106
106
|
const CARD_DATA_TIMEOUT_MS = 5_000;
|
|
107
107
|
const REPAIR_LINK_WAIT_MS = 15_000;
|
|
108
108
|
const REPAIR_POLL_INTERVAL_MS = 1_000;
|
|
109
|
-
const
|
|
110
|
-
'starting', 'qr_ready', 'polling', 'slow_down', 'domain_switched',
|
|
109
|
+
const REPAIR_AUTHORIZATION_STATES = new Set([
|
|
110
|
+
'starting', 'qr_ready', 'polling', 'slow_down', 'domain_switched',
|
|
111
111
|
]);
|
|
112
|
+
const REPAIR_ACTIVE_STATES = new Set([...REPAIR_AUTHORIZATION_STATES, 'saving']);
|
|
112
113
|
const REPAIR_TERMINAL_STATES = new Set([
|
|
113
114
|
'succeeded', 'expired', 'cancelled', 'error',
|
|
114
115
|
]);
|
|
@@ -150,7 +151,7 @@ function artifactFailureText(fileName, error) {
|
|
|
150
151
|
const name = String(fileName ?? t('结果文件')).replace(/[\r\n]+/g, ' ').trim() || t('结果文件');
|
|
151
152
|
switch (error?.code) {
|
|
152
153
|
case 'artifact-permission-required':
|
|
153
|
-
return t('结果文件「{name}
|
|
154
|
+
return t('结果文件「{name}」已生成,但机器人缺少飞书文件上传权限 im:resource。请私聊机器人执行 /repair 命令,或者在插件页面点击“补全权限”按钮并扫码。完成飞书要求的发布审批后重试。', { name });
|
|
154
155
|
case 'artifact-too-large':
|
|
155
156
|
return t('结果文件「{name}」超过飞书 30 MB 上限,未发送。', { name });
|
|
156
157
|
case 'artifact-empty':
|
|
@@ -410,7 +411,6 @@ export class FeishuHarnessBridge {
|
|
|
410
411
|
#botOpenId;
|
|
411
412
|
#groupResponseMode;
|
|
412
413
|
#repair;
|
|
413
|
-
#repairOwnerOpenIds;
|
|
414
414
|
#repairAttempt = null;
|
|
415
415
|
#repairMonitorVersion = 0;
|
|
416
416
|
#repairPollIntervalMs;
|
|
@@ -443,7 +443,6 @@ export class FeishuHarnessBridge {
|
|
|
443
443
|
botOpenId,
|
|
444
444
|
groupResponseMode = FEISHU_GROUP_RESPONSE_MODES.ALL,
|
|
445
445
|
repair,
|
|
446
|
-
repairOwnerOpenIds,
|
|
447
446
|
repairPollIntervalMs = REPAIR_POLL_INTERVAL_MS,
|
|
448
447
|
repairLinkWaitMs = REPAIR_LINK_WAIT_MS,
|
|
449
448
|
cardDataTimeoutMs = CARD_DATA_TIMEOUT_MS,
|
|
@@ -480,10 +479,6 @@ export class FeishuHarnessBridge {
|
|
|
480
479
|
this.#botOpenId = nonEmptyString(botOpenId);
|
|
481
480
|
this.#groupResponseMode = normalizeFeishuGroupResponseMode(groupResponseMode);
|
|
482
481
|
this.#repair = repair ?? null;
|
|
483
|
-
const repairOwners = repairOwnerOpenIds ?? allowedSenderOpenIds;
|
|
484
|
-
this.#repairOwnerOpenIds = new Set(
|
|
485
|
-
[...(repairOwners ?? [])].filter((value) => typeof value === 'string' && value && value !== '*'),
|
|
486
|
-
);
|
|
487
482
|
this.#repairPollIntervalMs = repairPollIntervalMs;
|
|
488
483
|
this.#repairLinkWaitMs = repairLinkWaitMs;
|
|
489
484
|
this.#cardDataTimeoutMs = cardDataTimeoutMs;
|
|
@@ -1075,13 +1070,8 @@ export class FeishuHarnessBridge {
|
|
|
1075
1070
|
return;
|
|
1076
1071
|
}
|
|
1077
1072
|
const actorOpenId = strictSenderOpenId(event);
|
|
1078
|
-
if (!actorOpenId
|
|
1079
|
-
await this.#send(
|
|
1080
|
-
event.message.chat_id,
|
|
1081
|
-
this.#repairOwnerOpenIds.size === 0
|
|
1082
|
-
? t('当前机器人没有可验证的接入者身份,不能从聊天发起修复;请先在插件页设置管理员。')
|
|
1083
|
-
: t('此操作只能由机器人接入者在私聊中发起,未进行任何修改。'),
|
|
1084
|
-
);
|
|
1073
|
+
if (!actorOpenId) {
|
|
1074
|
+
await this.#send(event.message.chat_id, t('无法识别当前发送者,未发起修复。'));
|
|
1085
1075
|
return;
|
|
1086
1076
|
}
|
|
1087
1077
|
if (!this.#repair) {
|
|
@@ -1110,7 +1100,7 @@ export class FeishuHarnessBridge {
|
|
|
1110
1100
|
return;
|
|
1111
1101
|
}
|
|
1112
1102
|
if (attempt.actorOpenId !== actorOpenId) {
|
|
1113
|
-
await this.#send(chatId, t('
|
|
1103
|
+
await this.#send(chatId, t('另一位用户正在修复该机器人,本次不会显示其授权信息。'));
|
|
1114
1104
|
return;
|
|
1115
1105
|
}
|
|
1116
1106
|
if (operation === 'cancel') {
|
|
@@ -1162,21 +1152,53 @@ export class FeishuHarnessBridge {
|
|
|
1162
1152
|
|
|
1163
1153
|
async #startRepair({ actorOpenId, chatId }) {
|
|
1164
1154
|
const previous = this.#repairAttempt;
|
|
1155
|
+
let restarted = false;
|
|
1165
1156
|
if (previous && REPAIR_ACTIVE_STATES.has(previous.snapshot.state)) {
|
|
1166
1157
|
if (previous.actorOpenId !== actorOpenId) {
|
|
1167
|
-
await this.#send(chatId, t('
|
|
1158
|
+
await this.#send(chatId, t('另一位用户正在修复该机器人,本次不会显示其授权信息。'));
|
|
1168
1159
|
return;
|
|
1169
1160
|
}
|
|
1161
|
+
let current;
|
|
1170
1162
|
try {
|
|
1171
|
-
|
|
1172
|
-
if (REPAIR_ACTIVE_STATES.has(current.state) && previous.verificationUrl) {
|
|
1173
|
-
await this.#sendRepairLink(chatId, previous.verificationUrl, current, { existing: true });
|
|
1174
|
-
return;
|
|
1175
|
-
}
|
|
1163
|
+
current = await this.#refreshRepairAttempt(previous);
|
|
1176
1164
|
} catch {
|
|
1177
1165
|
await this.#send(chatId, t('暂时无法查询修复状态,请稍后重试。'));
|
|
1178
1166
|
return;
|
|
1179
1167
|
}
|
|
1168
|
+
// Once Feishu has accepted the update, starting another attempt could
|
|
1169
|
+
// race the credential swap and callback probe. Status commands remain
|
|
1170
|
+
// available while that non-cancellable convergence is in progress.
|
|
1171
|
+
if (current.state === 'saving') {
|
|
1172
|
+
await this.#send(chatId, this.#repairStatusText(current));
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
// Feishu launcher links carry one-time user codes. Opening one with the
|
|
1176
|
+
// wrong Open Platform account can consume it even though authorization
|
|
1177
|
+
// did not succeed, so a fresh bare /repair must replace a still-waiting
|
|
1178
|
+
// attempt instead of redisplaying the same unusable URL.
|
|
1179
|
+
if (REPAIR_AUTHORIZATION_STATES.has(current.state)) {
|
|
1180
|
+
let cancelled;
|
|
1181
|
+
try {
|
|
1182
|
+
cancelled = repairSnapshot(
|
|
1183
|
+
await this.#repair.cancel(this.#repairArgs(previous)),
|
|
1184
|
+
{ botId: this.#botId },
|
|
1185
|
+
);
|
|
1186
|
+
previous.snapshot = cancelled;
|
|
1187
|
+
} catch {
|
|
1188
|
+
await this.#send(chatId, t('暂时无法取消旧修复任务,未生成新链接;请稍后重试。'));
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (!REPAIR_TERMINAL_STATES.has(cancelled.state)) {
|
|
1192
|
+
await this.#send(chatId, this.#repairStatusText(cancelled));
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
previous.stopped = true;
|
|
1196
|
+
this.#repairMonitorVersion += 1;
|
|
1197
|
+
restarted = cancelled.state === 'cancelled';
|
|
1198
|
+
} else if (REPAIR_TERMINAL_STATES.has(current.state)) {
|
|
1199
|
+
previous.stopped = true;
|
|
1200
|
+
this.#repairMonitorVersion += 1;
|
|
1201
|
+
}
|
|
1180
1202
|
}
|
|
1181
1203
|
|
|
1182
1204
|
let snapshot;
|
|
@@ -1225,7 +1247,7 @@ export class FeishuHarnessBridge {
|
|
|
1225
1247
|
await this.#send(chatId, t('飞书未返回授权链接,已中止本次修复。'));
|
|
1226
1248
|
return;
|
|
1227
1249
|
}
|
|
1228
|
-
await this.#sendRepairLink(chatId, attempt.verificationUrl, snapshot);
|
|
1250
|
+
await this.#sendRepairLink(chatId, attempt.verificationUrl, snapshot, { restarted });
|
|
1229
1251
|
this.#monitorRepair(attempt);
|
|
1230
1252
|
}
|
|
1231
1253
|
|
|
@@ -1303,15 +1325,15 @@ export class FeishuHarnessBridge {
|
|
|
1303
1325
|
});
|
|
1304
1326
|
}
|
|
1305
1327
|
|
|
1306
|
-
async #sendRepairLink(chatId, url, snapshot, {
|
|
1328
|
+
async #sendRepairLink(chatId, url, snapshot, { restarted = false } = {}) {
|
|
1307
1329
|
const remaining = snapshot.remainingSeconds
|
|
1308
1330
|
?? (snapshot.expiresAt ? Math.max(0, Math.ceil((snapshot.expiresAt - Date.now()) / 1000)) : null);
|
|
1309
1331
|
const expiry = remaining === null
|
|
1310
1332
|
? t('链接为短期有效')
|
|
1311
1333
|
: t('链接约 {minutes} 分钟后过期', { minutes: Math.max(1, Math.ceil(remaining / 60)) });
|
|
1312
1334
|
await this.#send(chatId, [
|
|
1313
|
-
|
|
1314
|
-
t('
|
|
1335
|
+
restarted ? t('旧授权链接已作废,已生成新的修复链接。') : t('🔧 准备补全权限与回调。'),
|
|
1336
|
+
t('本次最多增量添加三项:卡片回调 card.action.trigger;飞书显示为“获取单聊、群组消息”的租户权限 im:message:readonly(用于读取用户消息中的图片或文件);以及 im:resource(用于上传机器人发送的图片或文件)。确认页只会显示当前缺少的项;若出现上述范围之外的配置,请取消。'),
|
|
1315
1337
|
'',
|
|
1316
1338
|
t('当前设备直接打开:'),
|
|
1317
1339
|
url,
|
|
@@ -124,7 +124,7 @@ function backButton() {
|
|
|
124
124
|
* - presetCatalog: object|null (预设目录, {items,defaultId,_currentId})
|
|
125
125
|
* - modelCatalog: object|null (模型目录, {groups,current})
|
|
126
126
|
* Number fallback: 1=续写 2=新会话 3=会话列表 4=状态
|
|
127
|
-
* 5
|
|
127
|
+
* 5=补全权限 6=帮助.
|
|
128
128
|
*/
|
|
129
129
|
export function menuCard(ctx) {
|
|
130
130
|
const {
|
|
@@ -323,7 +323,7 @@ export function menuCard(ctx) {
|
|
|
323
323
|
|
|
324
324
|
// 命令与数字兜底说明
|
|
325
325
|
elements.push({ tag: 'div', text: markdown(t(
|
|
326
|
-
'**数字兜底**\n**1**工作区列表 · **2**新会话 · **3**会话列表 · **4**状态\n**5
|
|
326
|
+
'**数字兜底**\n**1**工作区列表 · **2**新会话 · **3**会话列表 · **4**状态\n**5**🔧补全权限 · **6**帮助',
|
|
327
327
|
)) });
|
|
328
328
|
return cardWith(t('🤖 助手中心'), elements);
|
|
329
329
|
}
|
|
@@ -495,6 +495,7 @@ export function menuHelpText() {
|
|
|
495
495
|
'',
|
|
496
496
|
'📊 状态 / 压缩',
|
|
497
497
|
'/status 连接状态',
|
|
498
|
+
'/version 查看插件版本',
|
|
498
499
|
'/compact 压缩当前会话上下文',
|
|
499
500
|
'/archived on/off 会话列表显示/隐藏归档',
|
|
500
501
|
'',
|
|
@@ -521,7 +522,7 @@ export function menuHelpText() {
|
|
|
521
522
|
'🎮 任务控制',
|
|
522
523
|
'/stop 停止当前任务',
|
|
523
524
|
'/steer 指令 给 Agent 补充指令',
|
|
524
|
-
'/repair
|
|
525
|
+
'/repair 补全飞书权限与卡片回调',
|
|
525
526
|
].map((line) => t(line)).join('\n');
|
|
526
527
|
}
|
|
527
528
|
|
|
@@ -572,14 +573,14 @@ const HELP_TEXT_COMMANDS = [
|
|
|
572
573
|
'`/batch` — 开启批量输入(仅私聊,最多 10 条文字)',
|
|
573
574
|
'`/send` — 提交当前批次',
|
|
574
575
|
'`/cancel` — 取消当前批次',
|
|
575
|
-
'`/repair` —
|
|
576
|
+
'`/repair` — 补全飞书权限与卡片回调',
|
|
576
577
|
].join('\n');
|
|
577
578
|
|
|
578
579
|
const HELP_NUMBER_FALLBACK = [
|
|
579
580
|
'**💡 数字兜底**',
|
|
580
581
|
'回复数字快速操作:',
|
|
581
582
|
'**1**工作区列表 · **2**新会话 · **3**会话/关注',
|
|
582
|
-
|
|
583
|
+
'**4**状态 · **5**补全权限 · **6**帮助',
|
|
583
584
|
].join('\n');
|
|
584
585
|
|
|
585
586
|
export function helpCard(extraTextLines = []) {
|
|
@@ -589,7 +590,10 @@ export function helpCard(extraTextLines = []) {
|
|
|
589
590
|
const elements = [
|
|
590
591
|
{ tag: 'div', text: markdown(t(HELP_CARD_FEATURES)) },
|
|
591
592
|
{ tag: 'hr' },
|
|
592
|
-
{ tag: 'div', text: markdown(
|
|
593
|
+
{ tag: 'div', text: markdown([
|
|
594
|
+
t(HELP_TEXT_COMMANDS),
|
|
595
|
+
t('`/version` — 查看插件版本'),
|
|
596
|
+
].join('\n') + extraText) },
|
|
593
597
|
{ tag: 'hr' },
|
|
594
598
|
{ tag: 'div', text: markdown(t(HELP_NUMBER_FALLBACK)) },
|
|
595
599
|
{ tag: 'hr' },
|
|
@@ -267,7 +267,6 @@ export class FeishuRuntime {
|
|
|
267
267
|
botOpenId: this.#botOpenId,
|
|
268
268
|
groupResponseMode: this.#groupResponseMode,
|
|
269
269
|
repair: this.#repair,
|
|
270
|
-
repairOwnerOpenIds: new Set(this.#ownerOpenIds.filter((value) => value !== '*')),
|
|
271
270
|
replyTimeoutMs: this.#replyTimeoutMs,
|
|
272
271
|
signal,
|
|
273
272
|
logger: this.#logger,
|
|
@@ -5,7 +5,7 @@ const FEISHU_MISSING_MESSAGE_SCOPE_CODE = 99991672;
|
|
|
5
5
|
const FEISHU_ERROR_BODY_LIMIT = 64 * 1024;
|
|
6
6
|
const FEISHU_ERROR_BODY_TIMEOUT_MS = 1_000;
|
|
7
7
|
const FEISHU_IMAGE_PERMISSION_MESSAGE =
|
|
8
|
-
'
|
|
8
|
+
'飞书机器人缺少图片读取权限 im:message:readonly(飞书显示为“获取单聊、群组消息”)。请私聊机器人执行 /repair 命令,或者在插件页面点击“补全权限”按钮并扫码。按飞书提示发布新版本、完成必要审批后,再重新发送图片。';
|
|
9
9
|
|
|
10
10
|
export function conversationKey(event) {
|
|
11
11
|
const chatType = event?.message?.chat_type;
|
|
@@ -869,22 +869,6 @@ export class MultiBotDshFeishuController {
|
|
|
869
869
|
'Feishu returned credentials for a different account domain.',
|
|
870
870
|
);
|
|
871
871
|
}
|
|
872
|
-
if (record.initiator.actorOpenId && record.initiator.actorOpenId !== ownerOpenId) {
|
|
873
|
-
throw this.#callbackRepairError(
|
|
874
|
-
record,
|
|
875
|
-
'repair_owner_mismatch',
|
|
876
|
-
'The Feishu repair was confirmed by a different operator.',
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
if (!target.ownerOpenIds.includes(ALL_VISIBLE_SENDERS)
|
|
880
|
-
&& !target.ownerOpenIds.includes(ownerOpenId)) {
|
|
881
|
-
throw this.#callbackRepairError(
|
|
882
|
-
record,
|
|
883
|
-
'repair_owner_mismatch',
|
|
884
|
-
'The Feishu repair operator is not an owner of this configured bot.',
|
|
885
|
-
);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
872
|
record.stage = 'verifying_identity';
|
|
889
873
|
let verified;
|
|
890
874
|
try {
|
|
@@ -1008,7 +992,7 @@ export class MultiBotDshFeishuController {
|
|
|
1008
992
|
record.stage = 'awaiting_callback';
|
|
1009
993
|
try {
|
|
1010
994
|
const proof = await runtime.beginCardActionProbe({
|
|
1011
|
-
expectedOperatorOpenId: ownerOpenId,
|
|
995
|
+
expectedOperatorOpenId: record.initiator.actorOpenId ?? ownerOpenId,
|
|
1012
996
|
timeoutMs: this.#callbackProbeTimeoutMs,
|
|
1013
997
|
...(record.initiator.chatId ? { chatId: record.initiator.chatId } : {}),
|
|
1014
998
|
});
|