@xmanrui/dsh-im 2.3.0 → 2.5.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 +9 -4
- package/README.md +9 -4
- package/lib/client.js +411 -156
- package/lib/index.js +218 -210
- package/package.json +1 -1
- package/plugin-src/client/channel-card-meta.js +36 -1
- package/plugin-src/client/channels/dingtalk/api.js +2 -0
- package/plugin-src/client/channels/dingtalk/index.js +9 -1
- package/plugin-src/client/channels/feishu/api.js +3 -0
- package/plugin-src/client/channels/feishu/index.js +46 -28
- package/plugin-src/client/channels/feishu/styles.js +26 -1
- package/plugin-src/client/channels/qq/api.js +2 -0
- package/plugin-src/client/channels/qq/index.js +9 -1
- package/plugin-src/client/channels/shared/token-api.js +2 -0
- package/plugin-src/client/channels/shared/token-channel.js +9 -1
- package/plugin-src/client/channels/wecom/api.js +2 -0
- package/plugin-src/client/channels/wecom/index.js +9 -1
- package/plugin-src/client/channels/weixin/api.js +2 -10
- package/plugin-src/client/channels/weixin/index.js +9 -5
- package/plugin-src/client/channels/whatsapp/api.js +2 -0
- package/plugin-src/client/channels/whatsapp/index.js +9 -1
- package/plugin-src/client/i18n.js +31 -25
- package/plugin-src/client/index.js +16 -2
- package/plugin-src/client/last-message-error.js +17 -0
- package/plugin-src/client/styles.js +5 -1
- package/plugin-src/host/channels/feishu/rpc.mjs +3 -0
- package/src/channels/dingtalk/dingtalk-api.mjs +1 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +50 -16
- package/src/channels/dingtalk/dingtalk-card-stream.mjs +2 -2
- package/src/channels/dingtalk/dingtalk-controller.mjs +2 -0
- package/src/channels/feishu/bridge.mjs +148 -70
- 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 +3 -17
- package/src/channels/feishu/repair-manager.mjs +7 -2
- package/src/channels/qq/qq-bridge.mjs +84 -28
- package/src/channels/qq/qq-controller.mjs +2 -0
- package/src/channels/shared/control-command.mjs +11 -1
- package/src/channels/shared/harness-client.mjs +40 -4
- package/src/channels/shared/i18n-en/feishu.mjs +27 -25
- package/src/channels/shared/i18n-en/shared-a.mjs +78 -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/message-failure.mjs +244 -0
- package/src/channels/shared/semantic/artifact-delivery.mjs +9 -2
- package/src/channels/shared/text-harness-bridge.mjs +66 -60
- package/src/channels/shared/token-bot-controller.mjs +2 -0
- package/src/channels/slack/slack-controller.mjs +2 -0
- package/src/channels/telegram/telegram-runtime.mjs +1 -0
- package/src/channels/wecom/wecom-bridge.mjs +50 -15
- package/src/channels/wecom/wecom-controller.mjs +2 -0
- package/src/channels/weixin/weixin-api.mjs +47 -0
- package/src/channels/weixin/weixin-bridge.mjs +262 -43
- package/src/channels/weixin/weixin-controller.mjs +2 -15
- package/src/channels/weixin/weixin-runtime.mjs +1 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +2 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from './message-utils.mjs';
|
|
10
10
|
import {
|
|
11
11
|
hasInboundImages,
|
|
12
|
+
imagePromptDiagnostic,
|
|
12
13
|
imagePromptUserMessage,
|
|
13
14
|
promptContentForMessage,
|
|
14
15
|
} from '../shared/image-prompt.mjs';
|
|
@@ -48,6 +49,12 @@ import { deliverOutboundArtifacts } from '../shared/semantic/artifact-delivery.m
|
|
|
48
49
|
import {
|
|
49
50
|
createDeliveryReceipt,
|
|
50
51
|
} from '../shared/semantic/delivery.mjs';
|
|
52
|
+
import {
|
|
53
|
+
channelDeliveryFailure,
|
|
54
|
+
clearLastMessageFailure,
|
|
55
|
+
messageFailureText,
|
|
56
|
+
setLastMessageFailure,
|
|
57
|
+
} from '../shared/message-failure.mjs';
|
|
51
58
|
import {
|
|
52
59
|
MENU_PAGE_SIZE,
|
|
53
60
|
PRESET_FOLLOW_DEFAULT_SENTINEL,
|
|
@@ -106,9 +113,10 @@ const CARD_OVERLOAD_NOTICE_COOLDOWN_MS = 5_000;
|
|
|
106
113
|
const CARD_DATA_TIMEOUT_MS = 5_000;
|
|
107
114
|
const REPAIR_LINK_WAIT_MS = 15_000;
|
|
108
115
|
const REPAIR_POLL_INTERVAL_MS = 1_000;
|
|
109
|
-
const
|
|
110
|
-
'starting', 'qr_ready', 'polling', 'slow_down', 'domain_switched',
|
|
116
|
+
const REPAIR_AUTHORIZATION_STATES = new Set([
|
|
117
|
+
'starting', 'qr_ready', 'polling', 'slow_down', 'domain_switched',
|
|
111
118
|
]);
|
|
119
|
+
const REPAIR_ACTIVE_STATES = new Set([...REPAIR_AUTHORIZATION_STATES, 'saving']);
|
|
112
120
|
const REPAIR_TERMINAL_STATES = new Set([
|
|
113
121
|
'succeeded', 'expired', 'cancelled', 'error',
|
|
114
122
|
]);
|
|
@@ -150,7 +158,7 @@ function artifactFailureText(fileName, error) {
|
|
|
150
158
|
const name = String(fileName ?? t('结果文件')).replace(/[\r\n]+/g, ' ').trim() || t('结果文件');
|
|
151
159
|
switch (error?.code) {
|
|
152
160
|
case 'artifact-permission-required':
|
|
153
|
-
return t('结果文件「{name}
|
|
161
|
+
return t('结果文件「{name}」已生成,但机器人缺少飞书文件上传权限 im:resource。请私聊机器人执行 /repair 命令,或者在插件页面点击“补全权限”按钮并扫码。完成飞书要求的发布审批后重试。', { name });
|
|
154
162
|
case 'artifact-too-large':
|
|
155
163
|
return t('结果文件「{name}」超过飞书 30 MB 上限,未发送。', { name });
|
|
156
164
|
case 'artifact-empty':
|
|
@@ -364,6 +372,7 @@ function ensureStatus(status) {
|
|
|
364
372
|
status.lastReplyAt ??= null;
|
|
365
373
|
status.lastRejectedAt ??= null;
|
|
366
374
|
status.lastError ??= null;
|
|
375
|
+
status.lastMessageError ??= null;
|
|
367
376
|
}
|
|
368
377
|
|
|
369
378
|
export class FeishuHarnessBridge {
|
|
@@ -410,7 +419,6 @@ export class FeishuHarnessBridge {
|
|
|
410
419
|
#botOpenId;
|
|
411
420
|
#groupResponseMode;
|
|
412
421
|
#repair;
|
|
413
|
-
#repairOwnerOpenIds;
|
|
414
422
|
#repairAttempt = null;
|
|
415
423
|
#repairMonitorVersion = 0;
|
|
416
424
|
#repairPollIntervalMs;
|
|
@@ -443,7 +451,6 @@ export class FeishuHarnessBridge {
|
|
|
443
451
|
botOpenId,
|
|
444
452
|
groupResponseMode = FEISHU_GROUP_RESPONSE_MODES.ALL,
|
|
445
453
|
repair,
|
|
446
|
-
repairOwnerOpenIds,
|
|
447
454
|
repairPollIntervalMs = REPAIR_POLL_INTERVAL_MS,
|
|
448
455
|
repairLinkWaitMs = REPAIR_LINK_WAIT_MS,
|
|
449
456
|
cardDataTimeoutMs = CARD_DATA_TIMEOUT_MS,
|
|
@@ -480,10 +487,6 @@ export class FeishuHarnessBridge {
|
|
|
480
487
|
this.#botOpenId = nonEmptyString(botOpenId);
|
|
481
488
|
this.#groupResponseMode = normalizeFeishuGroupResponseMode(groupResponseMode);
|
|
482
489
|
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
490
|
this.#repairPollIntervalMs = repairPollIntervalMs;
|
|
488
491
|
this.#repairLinkWaitMs = repairLinkWaitMs;
|
|
489
492
|
this.#cardDataTimeoutMs = cardDataTimeoutMs;
|
|
@@ -806,6 +809,30 @@ export class FeishuHarnessBridge {
|
|
|
806
809
|
return current;
|
|
807
810
|
}
|
|
808
811
|
|
|
812
|
+
#recordFailure(error, {
|
|
813
|
+
logLabel = 'operation',
|
|
814
|
+
logLevel = 'warn',
|
|
815
|
+
userMessage,
|
|
816
|
+
reason,
|
|
817
|
+
} = {}) {
|
|
818
|
+
this.#status.lastError = error?.message ?? String(error);
|
|
819
|
+
const failure = setLastMessageFailure(this.#status, error, { userMessage, reason });
|
|
820
|
+
this.#logger?.[logLevel]?.(
|
|
821
|
+
`[dsh-feishu] ${logLabel} failed [${failure.referenceId}]:`,
|
|
822
|
+
error?.message ?? String(error),
|
|
823
|
+
);
|
|
824
|
+
return failure;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async #sendFailure(chatId, error, options = {}) {
|
|
828
|
+
const failure = this.#recordFailure(error, options);
|
|
829
|
+
const text = options.appendMessage
|
|
830
|
+
? `${messageFailureText(failure)}\n\n${options.appendMessage}`
|
|
831
|
+
: messageFailureText(failure);
|
|
832
|
+
await this.#send(chatId, text).catch(() => undefined);
|
|
833
|
+
return failure;
|
|
834
|
+
}
|
|
835
|
+
|
|
809
836
|
async #handleMessageFailure(event, messageId, processingReaction, error) {
|
|
810
837
|
if (error?.code === 'turn-stopped') {
|
|
811
838
|
await this.#removeProcessingReaction(messageId, processingReaction);
|
|
@@ -818,15 +845,21 @@ export class FeishuHarnessBridge {
|
|
|
818
845
|
await this.#removeProcessingReaction(messageId, processingReaction);
|
|
819
846
|
return;
|
|
820
847
|
}
|
|
821
|
-
|
|
822
|
-
|
|
848
|
+
const userMessage = inboundFileUserMessage(error)
|
|
849
|
+
?? imagePromptUserMessage(error);
|
|
850
|
+
const failure = this.#recordFailure(error, {
|
|
851
|
+
logLabel: 'message handling',
|
|
852
|
+
logLevel: 'error',
|
|
853
|
+
userMessage,
|
|
854
|
+
reason: imagePromptDiagnostic(error)?.reason,
|
|
855
|
+
});
|
|
823
856
|
await this.#finishReaction(messageId, processingReaction, 'ERROR');
|
|
857
|
+
const failureText = error?.batchInputMessage
|
|
858
|
+
? `${messageFailureText(failure)}\n\n${error.batchInputMessage}`
|
|
859
|
+
: messageFailureText(failure);
|
|
824
860
|
await this.#send(
|
|
825
861
|
event.message.chat_id,
|
|
826
|
-
|
|
827
|
-
?? inboundFileUserMessage(error)
|
|
828
|
-
?? imagePromptUserMessage(error)
|
|
829
|
-
?? t('处理失败,请稍后重试。如果问题持续,请在 DeepSeek Harness 的飞书插件页面检查连接状态。'),
|
|
862
|
+
failureText,
|
|
830
863
|
).catch(() => undefined);
|
|
831
864
|
}
|
|
832
865
|
|
|
@@ -1030,7 +1063,11 @@ export class FeishuHarnessBridge {
|
|
|
1030
1063
|
const batchSubmission = event.batchSubmission ?? null;
|
|
1031
1064
|
let batchAskCompleted = false;
|
|
1032
1065
|
try {
|
|
1033
|
-
const
|
|
1066
|
+
const {
|
|
1067
|
+
receipt,
|
|
1068
|
+
artifactSendErrors,
|
|
1069
|
+
textDeliveryErrors = 0,
|
|
1070
|
+
} = await this.#answerWithStream(event, key, message, {
|
|
1034
1071
|
onAskComplete: batchSubmission
|
|
1035
1072
|
? () => {
|
|
1036
1073
|
batchAskCompleted = this.#batchInputs.complete(
|
|
@@ -1043,6 +1080,9 @@ export class FeishuHarnessBridge {
|
|
|
1043
1080
|
this.#status.messagesReplied += 1;
|
|
1044
1081
|
this.#status.lastReplyAt = new Date().toISOString();
|
|
1045
1082
|
this.#status.lastError = null;
|
|
1083
|
+
if (textDeliveryErrors === 0 && artifactSendErrors === 0) {
|
|
1084
|
+
clearLastMessageFailure(this.#status);
|
|
1085
|
+
}
|
|
1046
1086
|
return receipt;
|
|
1047
1087
|
} catch (error) {
|
|
1048
1088
|
if (batchSubmission && !batchAskCompleted) {
|
|
@@ -1054,6 +1094,9 @@ export class FeishuHarnessBridge {
|
|
|
1054
1094
|
if (failed.retained) {
|
|
1055
1095
|
const batchError = new Error(error?.message ?? String(error), { cause: error });
|
|
1056
1096
|
batchError.code = error?.code;
|
|
1097
|
+
batchError.providerCode = error?.providerCode;
|
|
1098
|
+
batchError.method = error?.method;
|
|
1099
|
+
if (Number.isInteger(error?.status)) batchError.status = error.status;
|
|
1057
1100
|
batchError.batchInputMessage = failed.message;
|
|
1058
1101
|
throw batchError;
|
|
1059
1102
|
}
|
|
@@ -1075,13 +1118,8 @@ export class FeishuHarnessBridge {
|
|
|
1075
1118
|
return;
|
|
1076
1119
|
}
|
|
1077
1120
|
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
|
-
);
|
|
1121
|
+
if (!actorOpenId) {
|
|
1122
|
+
await this.#send(event.message.chat_id, t('无法识别当前发送者,未发起修复。'));
|
|
1085
1123
|
return;
|
|
1086
1124
|
}
|
|
1087
1125
|
if (!this.#repair) {
|
|
@@ -1110,7 +1148,7 @@ export class FeishuHarnessBridge {
|
|
|
1110
1148
|
return;
|
|
1111
1149
|
}
|
|
1112
1150
|
if (attempt.actorOpenId !== actorOpenId) {
|
|
1113
|
-
await this.#send(chatId, t('
|
|
1151
|
+
await this.#send(chatId, t('另一位用户正在修复该机器人,本次不会显示其授权信息。'));
|
|
1114
1152
|
return;
|
|
1115
1153
|
}
|
|
1116
1154
|
if (operation === 'cancel') {
|
|
@@ -1162,21 +1200,53 @@ export class FeishuHarnessBridge {
|
|
|
1162
1200
|
|
|
1163
1201
|
async #startRepair({ actorOpenId, chatId }) {
|
|
1164
1202
|
const previous = this.#repairAttempt;
|
|
1203
|
+
let restarted = false;
|
|
1165
1204
|
if (previous && REPAIR_ACTIVE_STATES.has(previous.snapshot.state)) {
|
|
1166
1205
|
if (previous.actorOpenId !== actorOpenId) {
|
|
1167
|
-
await this.#send(chatId, t('
|
|
1206
|
+
await this.#send(chatId, t('另一位用户正在修复该机器人,本次不会显示其授权信息。'));
|
|
1168
1207
|
return;
|
|
1169
1208
|
}
|
|
1209
|
+
let current;
|
|
1170
1210
|
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
|
-
}
|
|
1211
|
+
current = await this.#refreshRepairAttempt(previous);
|
|
1176
1212
|
} catch {
|
|
1177
1213
|
await this.#send(chatId, t('暂时无法查询修复状态,请稍后重试。'));
|
|
1178
1214
|
return;
|
|
1179
1215
|
}
|
|
1216
|
+
// Once Feishu has accepted the update, starting another attempt could
|
|
1217
|
+
// race the credential swap and callback probe. Status commands remain
|
|
1218
|
+
// available while that non-cancellable convergence is in progress.
|
|
1219
|
+
if (current.state === 'saving') {
|
|
1220
|
+
await this.#send(chatId, this.#repairStatusText(current));
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
// Feishu launcher links carry one-time user codes. Opening one with the
|
|
1224
|
+
// wrong Open Platform account can consume it even though authorization
|
|
1225
|
+
// did not succeed, so a fresh bare /repair must replace a still-waiting
|
|
1226
|
+
// attempt instead of redisplaying the same unusable URL.
|
|
1227
|
+
if (REPAIR_AUTHORIZATION_STATES.has(current.state)) {
|
|
1228
|
+
let cancelled;
|
|
1229
|
+
try {
|
|
1230
|
+
cancelled = repairSnapshot(
|
|
1231
|
+
await this.#repair.cancel(this.#repairArgs(previous)),
|
|
1232
|
+
{ botId: this.#botId },
|
|
1233
|
+
);
|
|
1234
|
+
previous.snapshot = cancelled;
|
|
1235
|
+
} catch {
|
|
1236
|
+
await this.#send(chatId, t('暂时无法取消旧修复任务,未生成新链接;请稍后重试。'));
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (!REPAIR_TERMINAL_STATES.has(cancelled.state)) {
|
|
1240
|
+
await this.#send(chatId, this.#repairStatusText(cancelled));
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
previous.stopped = true;
|
|
1244
|
+
this.#repairMonitorVersion += 1;
|
|
1245
|
+
restarted = cancelled.state === 'cancelled';
|
|
1246
|
+
} else if (REPAIR_TERMINAL_STATES.has(current.state)) {
|
|
1247
|
+
previous.stopped = true;
|
|
1248
|
+
this.#repairMonitorVersion += 1;
|
|
1249
|
+
}
|
|
1180
1250
|
}
|
|
1181
1251
|
|
|
1182
1252
|
let snapshot;
|
|
@@ -1225,7 +1295,7 @@ export class FeishuHarnessBridge {
|
|
|
1225
1295
|
await this.#send(chatId, t('飞书未返回授权链接,已中止本次修复。'));
|
|
1226
1296
|
return;
|
|
1227
1297
|
}
|
|
1228
|
-
await this.#sendRepairLink(chatId, attempt.verificationUrl, snapshot);
|
|
1298
|
+
await this.#sendRepairLink(chatId, attempt.verificationUrl, snapshot, { restarted });
|
|
1229
1299
|
this.#monitorRepair(attempt);
|
|
1230
1300
|
}
|
|
1231
1301
|
|
|
@@ -1303,15 +1373,15 @@ export class FeishuHarnessBridge {
|
|
|
1303
1373
|
});
|
|
1304
1374
|
}
|
|
1305
1375
|
|
|
1306
|
-
async #sendRepairLink(chatId, url, snapshot, {
|
|
1376
|
+
async #sendRepairLink(chatId, url, snapshot, { restarted = false } = {}) {
|
|
1307
1377
|
const remaining = snapshot.remainingSeconds
|
|
1308
1378
|
?? (snapshot.expiresAt ? Math.max(0, Math.ceil((snapshot.expiresAt - Date.now()) / 1000)) : null);
|
|
1309
1379
|
const expiry = remaining === null
|
|
1310
1380
|
? t('链接为短期有效')
|
|
1311
1381
|
: t('链接约 {minutes} 分钟后过期', { minutes: Math.max(1, Math.ceil(remaining / 60)) });
|
|
1312
1382
|
await this.#send(chatId, [
|
|
1313
|
-
|
|
1314
|
-
t('
|
|
1383
|
+
restarted ? t('旧授权链接已作废,已生成新的修复链接。') : t('🔧 准备补全权限与回调。'),
|
|
1384
|
+
t('本次最多增量添加三项:卡片回调 card.action.trigger;飞书显示为“获取单聊、群组消息”的租户权限 im:message:readonly(用于读取用户消息中的图片或文件);以及 im:resource(用于上传机器人发送的图片或文件)。确认页只会显示当前缺少的项;若出现上述范围之外的配置,请取消。'),
|
|
1315
1385
|
'',
|
|
1316
1386
|
t('当前设备直接打开:'),
|
|
1317
1387
|
url,
|
|
@@ -1621,9 +1691,7 @@ export class FeishuHarnessBridge {
|
|
|
1621
1691
|
})
|
|
1622
1692
|
.catch(async (error) => {
|
|
1623
1693
|
if (this.#signal?.aborted) return;
|
|
1624
|
-
this.#
|
|
1625
|
-
this.#status.lastError = error?.message ?? String(error);
|
|
1626
|
-
await this.#send(entry.chatId, t('卡片操作失败,请稍后重试。')).catch(() => undefined);
|
|
1694
|
+
await this.#sendFailure(entry.chatId, error, { logLabel: 'card action' });
|
|
1627
1695
|
})
|
|
1628
1696
|
.finally(() => {
|
|
1629
1697
|
if (this.#cardActionInFlight.get(dedupeKey) === tracked) {
|
|
@@ -1986,8 +2054,7 @@ export class FeishuHarnessBridge {
|
|
|
1986
2054
|
},
|
|
1987
2055
|
);
|
|
1988
2056
|
} catch (error) {
|
|
1989
|
-
this.#
|
|
1990
|
-
await this.#send(chatId, t('暂时无法获取会话列表,请稍后重试。'));
|
|
2057
|
+
await this.#sendFailure(chatId, error, { logLabel: 'session list' });
|
|
1991
2058
|
}
|
|
1992
2059
|
}
|
|
1993
2060
|
|
|
@@ -2004,8 +2071,7 @@ export class FeishuHarnessBridge {
|
|
|
2004
2071
|
{ key, updateMessageId },
|
|
2005
2072
|
);
|
|
2006
2073
|
} catch (error) {
|
|
2007
|
-
this.#
|
|
2008
|
-
await this.#send(chatId, t('暂时无法获取工作区列表,请稍后重试。'));
|
|
2074
|
+
await this.#sendFailure(chatId, error, { logLabel: 'workspace list' });
|
|
2009
2075
|
}
|
|
2010
2076
|
}
|
|
2011
2077
|
|
|
@@ -2016,7 +2082,10 @@ export class FeishuHarnessBridge {
|
|
|
2016
2082
|
await this.#send(chatId, t('已绑定会话「{title}」\nID:{id}', { title, id: bound?.sessionId ?? sessionId }));
|
|
2017
2083
|
await this.#sendMenuCard(key, chatId, { updateMessageId });
|
|
2018
2084
|
} catch (error) {
|
|
2019
|
-
await this.#
|
|
2085
|
+
await this.#sendFailure(chatId, error, {
|
|
2086
|
+
logLabel: 'session binding',
|
|
2087
|
+
userMessage: t('绑定失败:{message}', { message: safeErrorText(error) }),
|
|
2088
|
+
});
|
|
2020
2089
|
}
|
|
2021
2090
|
}
|
|
2022
2091
|
|
|
@@ -2026,7 +2095,10 @@ export class FeishuHarnessBridge {
|
|
|
2026
2095
|
await this.#send(chatId, t('工作区已切换为:{workspace}', { workspace: current }));
|
|
2027
2096
|
await this.#sendMenuCard(key, chatId, { updateMessageId });
|
|
2028
2097
|
} catch (error) {
|
|
2029
|
-
await this.#
|
|
2098
|
+
await this.#sendFailure(chatId, error, {
|
|
2099
|
+
logLabel: 'workspace switch',
|
|
2100
|
+
userMessage: t('切换失败:{message}', { message: safeErrorText(error) }),
|
|
2101
|
+
});
|
|
2030
2102
|
}
|
|
2031
2103
|
}
|
|
2032
2104
|
|
|
@@ -2225,8 +2297,7 @@ export class FeishuHarnessBridge {
|
|
|
2225
2297
|
catalog._currentId = settings.agentPreset;
|
|
2226
2298
|
await this.#sendCard(chatId, presetCard(catalog), { key, updateMessageId });
|
|
2227
2299
|
} catch (error) {
|
|
2228
|
-
this.#
|
|
2229
|
-
await this.#send(chatId, t('暂时无法获取预设列表,请稍后重试。'));
|
|
2300
|
+
await this.#sendFailure(chatId, error, { logLabel: 'preset card' });
|
|
2230
2301
|
}
|
|
2231
2302
|
}
|
|
2232
2303
|
|
|
@@ -2251,8 +2322,7 @@ export class FeishuHarnessBridge {
|
|
|
2251
2322
|
}
|
|
2252
2323
|
await this.#sendCard(chatId, modelCard(catalog), { key, updateMessageId });
|
|
2253
2324
|
} catch (error) {
|
|
2254
|
-
this.#
|
|
2255
|
-
await this.#send(chatId, t('暂时无法获取模型列表,请稍后重试。'));
|
|
2325
|
+
await this.#sendFailure(chatId, error, { logLabel: 'model card' });
|
|
2256
2326
|
}
|
|
2257
2327
|
}
|
|
2258
2328
|
|
|
@@ -2280,8 +2350,7 @@ export class FeishuHarnessBridge {
|
|
|
2280
2350
|
}
|
|
2281
2351
|
await this.#send(chatId, lines.join('\n'));
|
|
2282
2352
|
} catch (error) {
|
|
2283
|
-
this.#
|
|
2284
|
-
await this.#send(chatId, t('暂时无法获取系统状态,请稍后重试。'));
|
|
2353
|
+
await this.#sendFailure(chatId, error, { logLabel: 'status text' });
|
|
2285
2354
|
}
|
|
2286
2355
|
}
|
|
2287
2356
|
|
|
@@ -2333,8 +2402,7 @@ export class FeishuHarnessBridge {
|
|
|
2333
2402
|
|
|
2334
2403
|
await this.#sendCard(chatId, statusCard(info), { key, updateMessageId });
|
|
2335
2404
|
} catch (error) {
|
|
2336
|
-
this.#
|
|
2337
|
-
await this.#send(chatId, t('暂时无法获取系统状态,请稍后重试。'));
|
|
2405
|
+
await this.#sendFailure(chatId, error, { logLabel: 'status card' });
|
|
2338
2406
|
}
|
|
2339
2407
|
}
|
|
2340
2408
|
|
|
@@ -2359,8 +2427,7 @@ export class FeishuHarnessBridge {
|
|
|
2359
2427
|
);
|
|
2360
2428
|
await this.#send(chatId, result?.message || t('上下文压缩失败。'));
|
|
2361
2429
|
} catch (error) {
|
|
2362
|
-
this.#
|
|
2363
|
-
await this.#send(chatId, t('上下文压缩失败,请稍后重试。'));
|
|
2430
|
+
await this.#sendFailure(chatId, error, { logLabel: 'compact' });
|
|
2364
2431
|
}
|
|
2365
2432
|
}
|
|
2366
2433
|
|
|
@@ -2383,8 +2450,7 @@ export class FeishuHarnessBridge {
|
|
|
2383
2450
|
}
|
|
2384
2451
|
await this.#send(chatId, result?.message || t('/stop 执行完成。'));
|
|
2385
2452
|
} catch (error) {
|
|
2386
|
-
this.#
|
|
2387
|
-
await this.#send(chatId, t('停止任务失败,请稍后重试。'));
|
|
2453
|
+
await this.#sendFailure(chatId, error, { logLabel: 'stop' });
|
|
2388
2454
|
}
|
|
2389
2455
|
}
|
|
2390
2456
|
|
|
@@ -2424,8 +2490,7 @@ export class FeishuHarnessBridge {
|
|
|
2424
2490
|
if (reply) await this.#send(chatId, reply);
|
|
2425
2491
|
}
|
|
2426
2492
|
} catch (error) {
|
|
2427
|
-
this.#
|
|
2428
|
-
await this.#send(chatId, t('预设重置失败,请稍后重试。'));
|
|
2493
|
+
await this.#sendFailure(chatId, error, { logLabel: 'preset reset' });
|
|
2429
2494
|
return;
|
|
2430
2495
|
}
|
|
2431
2496
|
try {
|
|
@@ -2448,8 +2513,7 @@ export class FeishuHarnessBridge {
|
|
|
2448
2513
|
if (reply) await this.#send(chatId, reply);
|
|
2449
2514
|
}
|
|
2450
2515
|
} catch (error) {
|
|
2451
|
-
this.#
|
|
2452
|
-
await this.#send(chatId, t('预设切换失败,请稍后重试。'));
|
|
2516
|
+
await this.#sendFailure(chatId, error, { logLabel: 'preset selection' });
|
|
2453
2517
|
return;
|
|
2454
2518
|
}
|
|
2455
2519
|
try {
|
|
@@ -2481,8 +2545,7 @@ export class FeishuHarnessBridge {
|
|
|
2481
2545
|
if (reply) await this.#send(chatId, reply);
|
|
2482
2546
|
}
|
|
2483
2547
|
} catch (error) {
|
|
2484
|
-
this.#
|
|
2485
|
-
await this.#send(chatId, t('模型切换失败,请稍后重试。'));
|
|
2548
|
+
await this.#sendFailure(chatId, error, { logLabel: 'model selection' });
|
|
2486
2549
|
return;
|
|
2487
2550
|
}
|
|
2488
2551
|
try {
|
|
@@ -3024,10 +3087,14 @@ export class FeishuHarnessBridge {
|
|
|
3024
3087
|
signal: this.#signal,
|
|
3025
3088
|
})
|
|
3026
3089
|
: undefined,
|
|
3027
|
-
|
|
3090
|
+
onFailure: (artifact, error) => setLastMessageFailure(this.#status, error, {
|
|
3091
|
+
userMessage: artifactFailureText(artifact?.fileName, error),
|
|
3092
|
+
reason: error?.code,
|
|
3093
|
+
}),
|
|
3094
|
+
sendFailureNotice: async (_artifact, _error, failure) => ({
|
|
3028
3095
|
messageId: await this.#send(
|
|
3029
3096
|
chatId,
|
|
3030
|
-
|
|
3097
|
+
messageFailureText(failure),
|
|
3031
3098
|
),
|
|
3032
3099
|
}),
|
|
3033
3100
|
logger: this.#logger,
|
|
@@ -3043,11 +3110,13 @@ export class FeishuHarnessBridge {
|
|
|
3043
3110
|
presentation: 'feishu-files',
|
|
3044
3111
|
}),
|
|
3045
3112
|
failureNoticeVisible: delivery.failureNoticeVisible,
|
|
3113
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
3046
3114
|
};
|
|
3047
3115
|
}
|
|
3048
3116
|
return {
|
|
3049
3117
|
receipt: delivery.receipt,
|
|
3050
3118
|
failureNoticeVisible: delivery.failureNoticeVisible,
|
|
3119
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
3051
3120
|
};
|
|
3052
3121
|
}
|
|
3053
3122
|
|
|
@@ -3088,7 +3157,7 @@ export class FeishuHarnessBridge {
|
|
|
3088
3157
|
},
|
|
3089
3158
|
);
|
|
3090
3159
|
} catch (error) {
|
|
3091
|
-
textSendError = error;
|
|
3160
|
+
textSendError = channelDeliveryFailure(error);
|
|
3092
3161
|
this.#logger.warn?.(
|
|
3093
3162
|
'[dsh-feishu] final text delivery failed; continuing with result files:',
|
|
3094
3163
|
error,
|
|
@@ -3101,8 +3170,11 @@ export class FeishuHarnessBridge {
|
|
|
3101
3170
|
if (textSendError && !artifactDispatched && !delivery.failureNoticeVisible) {
|
|
3102
3171
|
throw textSendError;
|
|
3103
3172
|
}
|
|
3173
|
+
if (textSendError && delivery.artifactSendErrors === 0) {
|
|
3174
|
+
setLastMessageFailure(this.#status, textSendError);
|
|
3175
|
+
}
|
|
3104
3176
|
this.#status.streamFallbacks = (this.#status.streamFallbacks ?? 0) + 1;
|
|
3105
|
-
return delivery
|
|
3177
|
+
return { ...delivery, textDeliveryErrors: textSendError ? 1 : 0 };
|
|
3106
3178
|
}
|
|
3107
3179
|
|
|
3108
3180
|
let promptStarted = false;
|
|
@@ -3155,7 +3227,7 @@ export class FeishuHarnessBridge {
|
|
|
3155
3227
|
},
|
|
3156
3228
|
);
|
|
3157
3229
|
} catch (fallbackError) {
|
|
3158
|
-
textSendError = fallbackError;
|
|
3230
|
+
textSendError = channelDeliveryFailure(fallbackError);
|
|
3159
3231
|
this.#logger.warn?.(
|
|
3160
3232
|
'[dsh-feishu] fallback text delivery failed; continuing with result files:',
|
|
3161
3233
|
fallbackError,
|
|
@@ -3173,8 +3245,11 @@ export class FeishuHarnessBridge {
|
|
|
3173
3245
|
if (textSendError && !artifactDispatched && !delivery.failureNoticeVisible) {
|
|
3174
3246
|
throw textSendError;
|
|
3175
3247
|
}
|
|
3248
|
+
if (textSendError && delivery.artifactSendErrors === 0) {
|
|
3249
|
+
setLastMessageFailure(this.#status, textSendError);
|
|
3250
|
+
}
|
|
3176
3251
|
this.#status.streamFallbacks = (this.#status.streamFallbacks ?? 0) + 1;
|
|
3177
|
-
return delivery
|
|
3252
|
+
return { ...delivery, textDeliveryErrors: textSendError ? 1 : 0 };
|
|
3178
3253
|
}
|
|
3179
3254
|
if (promptStarted) throw error;
|
|
3180
3255
|
|
|
@@ -3202,7 +3277,7 @@ export class FeishuHarnessBridge {
|
|
|
3202
3277
|
},
|
|
3203
3278
|
);
|
|
3204
3279
|
} catch (fallbackError) {
|
|
3205
|
-
textSendError = fallbackError;
|
|
3280
|
+
textSendError = channelDeliveryFailure(fallbackError);
|
|
3206
3281
|
this.#logger.warn?.(
|
|
3207
3282
|
'[dsh-feishu] fallback text delivery failed; continuing with result files:',
|
|
3208
3283
|
fallbackError,
|
|
@@ -3215,8 +3290,11 @@ export class FeishuHarnessBridge {
|
|
|
3215
3290
|
if (textSendError && !artifactDispatched && !delivery.failureNoticeVisible) {
|
|
3216
3291
|
throw textSendError;
|
|
3217
3292
|
}
|
|
3293
|
+
if (textSendError && delivery.artifactSendErrors === 0) {
|
|
3294
|
+
setLastMessageFailure(this.#status, textSendError);
|
|
3295
|
+
}
|
|
3218
3296
|
this.#status.streamFallbacks = (this.#status.streamFallbacks ?? 0) + 1;
|
|
3219
|
-
return delivery
|
|
3297
|
+
return { ...delivery, textDeliveryErrors: textSendError ? 1 : 0 };
|
|
3220
3298
|
}
|
|
3221
3299
|
const delivery = await this.#deliverArtifacts(
|
|
3222
3300
|
chatId,
|
|
@@ -3229,7 +3307,7 @@ export class FeishuHarnessBridge {
|
|
|
3229
3307
|
}),
|
|
3230
3308
|
);
|
|
3231
3309
|
this.#status.streamResponses = (this.#status.streamResponses ?? 0) + 1;
|
|
3232
|
-
return delivery
|
|
3310
|
+
return delivery;
|
|
3233
3311
|
}
|
|
3234
3312
|
|
|
3235
3313
|
async #processInteractionReply(event, messageId, key, expected, processingReaction) {
|
|
@@ -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;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { connectionTestMessage } from '../shared/connection-test.mjs';
|
|
3
|
+
import { publicMessageFailure } from '../shared/message-failure.mjs';
|
|
3
4
|
import { RegistrationManager } from './registration-manager.mjs';
|
|
4
5
|
import {
|
|
5
6
|
CALLBACK_REPAIR_OPERATION,
|
|
@@ -633,6 +634,7 @@ export class MultiBotDshFeishuController {
|
|
|
633
634
|
groupMessagePermissionGranted: config.groupMessagePermissionGranted === true,
|
|
634
635
|
bot: publicBot(config),
|
|
635
636
|
connection,
|
|
637
|
+
lastMessageError: publicMessageFailure(connection.lastMessageError),
|
|
636
638
|
error,
|
|
637
639
|
};
|
|
638
640
|
});
|
|
@@ -869,22 +871,6 @@ export class MultiBotDshFeishuController {
|
|
|
869
871
|
'Feishu returned credentials for a different account domain.',
|
|
870
872
|
);
|
|
871
873
|
}
|
|
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
874
|
record.stage = 'verifying_identity';
|
|
889
875
|
let verified;
|
|
890
876
|
try {
|
|
@@ -1008,7 +994,7 @@ export class MultiBotDshFeishuController {
|
|
|
1008
994
|
record.stage = 'awaiting_callback';
|
|
1009
995
|
try {
|
|
1010
996
|
const proof = await runtime.beginCardActionProbe({
|
|
1011
|
-
expectedOperatorOpenId: ownerOpenId,
|
|
997
|
+
expectedOperatorOpenId: record.initiator.actorOpenId ?? ownerOpenId,
|
|
1012
998
|
timeoutMs: this.#callbackProbeTimeoutMs,
|
|
1013
999
|
...(record.initiator.chatId ? { chatId: record.initiator.chatId } : {}),
|
|
1014
1000
|
});
|