@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
|
@@ -8,6 +8,7 @@ const EN = Object.freeze({
|
|
|
8
8
|
'IM机器人设置': 'IM bot settings',
|
|
9
9
|
'IM 渠道': 'IM channels',
|
|
10
10
|
'让 DeepSeek Harness 触手可及': 'DeepSeek Harness, always within reach',
|
|
11
|
+
'当前版本': 'Current version',
|
|
11
12
|
'AI Office': 'AI Office',
|
|
12
13
|
'(实验功能)': '(Experimental)',
|
|
13
14
|
'AI Office 设置': 'AI Office settings',
|
|
@@ -113,6 +114,9 @@ const EN = Object.freeze({
|
|
|
113
114
|
'消息通道': 'Message channel',
|
|
114
115
|
'查看消息通道说明': 'View message channel details',
|
|
115
116
|
'最近检查': 'Last checked',
|
|
117
|
+
'最近一条消息处理失败': 'Latest message failed',
|
|
118
|
+
'错误码': 'Code',
|
|
119
|
+
'参考号': 'Reference',
|
|
116
120
|
'当前工作区': 'Current workspace',
|
|
117
121
|
'选择目录': 'Choose folder',
|
|
118
122
|
'选择机器人工作区目录': 'Select bot workspace folder',
|
|
@@ -231,7 +235,7 @@ const EN = Object.freeze({
|
|
|
231
235
|
'保持本页打开,等待新机器人的长连接就绪': 'Keep this page open until the bot connection is ready',
|
|
232
236
|
'在飞书中打开': 'Open in Feishu',
|
|
233
237
|
'取消添加': 'Cancel',
|
|
234
|
-
'
|
|
238
|
+
'使用飞书扫码补全权限': 'Scan with Feishu to complete permissions',
|
|
235
239
|
'使用飞书确认群消息权限': 'Confirm group-message permission with Feishu',
|
|
236
240
|
'扫码会更新现有飞书应用,只增量开通“获取群组中所有消息”权限;不会创建新应用。确认后会自动启用“响应所有群消息”,其他机器人不受影响。': '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
241
|
'核对现有应用,并确认“获取群组中所有消息”权限': 'Review the existing app and confirm the “Read all messages in associated group chat” permission',
|
|
@@ -242,18 +246,20 @@ const EN = Object.freeze({
|
|
|
242
246
|
'权限配置已提交,正在启用全部消息模式并重连此机器人;此阶段无法取消,其他机器人不会中断。': '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
247
|
'正在为现有飞书应用申请群消息权限二维码,请稍候。': 'Requesting a group-message permission QR code for the existing Feishu app…',
|
|
244
248
|
'群消息权限没有开通完成': 'Group-message permission was not granted',
|
|
245
|
-
'
|
|
246
|
-
'
|
|
247
|
-
'
|
|
248
|
-
'
|
|
249
|
-
'
|
|
250
|
-
'
|
|
251
|
-
'
|
|
249
|
+
'扫码会更新现有飞书应用,最多增量补充卡片按钮回调、读取用户消息内图片或文件所需的 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.',
|
|
250
|
+
'核对现有应用名称,并确认只新增当前缺少的上述配置': 'Review the existing app name and confirm that only the missing items described above are added',
|
|
251
|
+
'保持本页打开,等待权限与回调补全完成': 'Keep this page open until permissions and the callback are complete',
|
|
252
|
+
'取消补全': 'Cancel setup',
|
|
253
|
+
'已确认,正在完成权限与回调配置': 'Confirmed. Completing permissions and callback setup',
|
|
254
|
+
'正在准备权限补全二维码': 'Preparing the permission-completion QR code',
|
|
255
|
+
'配置已提交,正在保存权限、验证卡片回调并重连此机器人;此阶段无法取消,其他机器人不会中断。': '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
256
|
'正在为现有飞书应用申请一次性更新二维码,请稍候。': 'Requesting a one-time update QR code for the existing Feishu app…',
|
|
253
|
-
'
|
|
254
|
-
'
|
|
257
|
+
'权限与回调没有补全完成': 'Permissions and callback setup did not finish',
|
|
258
|
+
'补全权限': 'Complete permissions',
|
|
259
|
+
'补全范围': 'Completion scope',
|
|
260
|
+
'最多增量添加卡片回调 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
261
|
'等待扫码…': 'Waiting for scan…',
|
|
256
|
-
'
|
|
262
|
+
'飞书服务返回了不匹配的权限补全二维码': 'Feishu returned a permission-completion QR code for a different bot',
|
|
257
263
|
'飞书服务返回了不匹配的群消息权限二维码': 'Feishu returned a group-message permission QR code for a different bot',
|
|
258
264
|
'飞书服务返回的修复信息缺少 botId': 'Feishu repair status is missing the bot ID',
|
|
259
265
|
'飞书服务返回的应用更新信息缺少 botId': 'Feishu app-update status is missing the bot ID',
|
|
@@ -261,16 +267,16 @@ const EN = Object.freeze({
|
|
|
261
267
|
'此机器人': 'this bot',
|
|
262
268
|
'用于为${botName}开通群消息权限的一次性授权二维码': 'One-time QR code for granting group-message permission to ${botName}',
|
|
263
269
|
'正在为「${botName}」开通群消息权限': 'Granting group-message permission to “${botName}”',
|
|
264
|
-
'${botName ?? "机器人"}
|
|
270
|
+
'${botName ?? "机器人"}的权限补全二维码已生成,请使用飞书扫码。': 'Permission-completion QR code generated for ${botName ?? "bot"}. Scan it with Feishu.',
|
|
265
271
|
'${botName ?? "机器人"}的群消息权限二维码已生成,请使用飞书确认。': 'Group-message permission QR code generated for ${botName ?? "bot"}. Confirm it with Feishu.',
|
|
266
272
|
'${targetBotName}已开通群消息权限,并启用“响应所有群消息”。': '${targetBotName} now has group-message permission and “Respond to all group messages” is enabled.',
|
|
267
273
|
'${targetBot.bot.name}已开通群消息权限,并启用“响应所有群消息”。': '${targetBot.bot.name} now has group-message permission and “Respond to all group messages” is enabled.',
|
|
268
274
|
'${targetBot.bot.name}已连接,可以在飞书中开始聊天。': '${targetBot.bot.name} is connected and ready to chat in Feishu.',
|
|
269
|
-
'
|
|
275
|
+
'已取消补全权限与回调。': 'Completing permissions and the callback was cancelled.',
|
|
270
276
|
'已取消群消息权限授权。': 'Group-message permission authorization was cancelled.',
|
|
271
|
-
'
|
|
277
|
+
'权限与回调已更新,但暂时无法确认机器人连接状态': 'Permissions and the callback were updated, but the bot connection could not be confirmed yet',
|
|
272
278
|
'群消息权限已更新,但暂时无法确认机器人连接状态': 'The group-message permission was updated, but the bot connection could not be confirmed yet',
|
|
273
|
-
'
|
|
279
|
+
'飞书权限与回调补全失败': 'Could not complete Feishu permissions and callback setup',
|
|
274
280
|
'飞书群消息权限开通失败': 'Could not grant the Feishu group-message permission',
|
|
275
281
|
'请先完成当前飞书授权操作,再开通群消息权限。': 'Finish the current Feishu authorization before granting group-message permission.',
|
|
276
282
|
'已确认,正在连接新机器人': 'Confirmed. Connecting the new bot',
|
|
@@ -598,22 +604,22 @@ function translateDynamic(text) {
|
|
|
598
604
|
if (match) return `Remove ${match[1]} from DeepSeek Harness`;
|
|
599
605
|
match = /^(.+)的飞书授权流程$/.exec(text);
|
|
600
606
|
if (match) return `Feishu authorization flow for ${match[1]}`;
|
|
601
|
-
match =
|
|
602
|
-
if (match) return `One-time QR code for
|
|
607
|
+
match = /^用于为(.+)补全权限与回调的一次性授权二维码$/.exec(text);
|
|
608
|
+
if (match) return `One-time QR code for completing permissions and the callback for ${match[1]}`;
|
|
603
609
|
match = /^用于为(.+)开通群消息权限的一次性授权二维码$/.exec(text);
|
|
604
610
|
if (match) return `One-time QR code for granting group-message permission to ${match[1]}`;
|
|
605
|
-
match =
|
|
606
|
-
if (match) return `
|
|
611
|
+
match = /^正在为「(.+)」补全权限与回调$/.exec(text);
|
|
612
|
+
if (match) return `Completing permissions and the callback for “${match[1]}”`;
|
|
607
613
|
match = /^正在为「(.+)」开通群消息权限$/.exec(text);
|
|
608
614
|
if (match) return `Granting group-message permission to “${match[1]}”`;
|
|
609
|
-
match =
|
|
610
|
-
if (match) return `
|
|
611
|
-
match = /^(.+)
|
|
612
|
-
if (match) return `
|
|
615
|
+
match = /^为(.+)补全权限与回调$/.exec(text);
|
|
616
|
+
if (match) return `Complete permissions and the callback for ${match[1]}`;
|
|
617
|
+
match = /^(.+)的权限补全二维码已生成,请使用飞书扫码。$/.exec(text);
|
|
618
|
+
if (match) return `Permission-completion QR code generated for ${match[1]}. Scan it with Feishu.`;
|
|
613
619
|
match = /^(.+)的群消息权限二维码已生成,请使用飞书确认。$/.exec(text);
|
|
614
620
|
if (match) return `Group-message permission QR code generated for ${match[1]}. Confirm it with Feishu.`;
|
|
615
|
-
match = /^(.+)
|
|
616
|
-
if (match) return `
|
|
621
|
+
match = /^(.+)的权限与回调已补全。$/.exec(text);
|
|
622
|
+
if (match) return `Permissions and the callback completed for ${match[1]}.`;
|
|
617
623
|
match = /^(.+)已开通群消息权限,并启用“响应所有群消息”。$/.exec(text);
|
|
618
624
|
if (match) return `${match[1]} now has group-message permission and “Respond to all group messages” is enabled.`;
|
|
619
625
|
match = /^(检查连接|重试连接)(.+)$/.exec(text);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import manifest from '../../package.json' with { type: 'json' };
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
DingtalkLogoGlyph,
|
|
@@ -51,6 +52,7 @@ import { WorkspaceDirectoryPickerContext } from './workspace-editor.js';
|
|
|
51
52
|
|
|
52
53
|
export const name = 'im-settings';
|
|
53
54
|
export const inject = ['slots', 'connection', 'locale', 'workspaces'];
|
|
55
|
+
export const IM_PLUGIN_VERSION = manifest.version;
|
|
54
56
|
|
|
55
57
|
const CHANNELS = Object.freeze([
|
|
56
58
|
{ id: 'weixin', label: '微信' },
|
|
@@ -159,6 +161,7 @@ export function IMSettingsTab({
|
|
|
159
161
|
}) {
|
|
160
162
|
const [selected, setSelected] = React.useState('weixin');
|
|
161
163
|
const [loopbackRecovery, setLoopbackRecovery] = React.useState(null);
|
|
164
|
+
const versionTooltipId = React.useId();
|
|
162
165
|
const githubTooltipId = React.useId();
|
|
163
166
|
const active = CHANNELS.find((channel) => channel.id === selected) ?? CHANNELS[0];
|
|
164
167
|
const reportLoopbackRecovery = React.useCallback((recovery) => {
|
|
@@ -195,9 +198,20 @@ export function IMSettingsTab({
|
|
|
195
198
|
return h(WorkspaceDirectoryPickerContext.Provider, { value: workspaceDirectoryPicker },
|
|
196
199
|
h('section', { className: 'dim-page', 'aria-label': 'IM机器人设置' },
|
|
197
200
|
h('header', { className: 'dim-title' },
|
|
198
|
-
h('div', {
|
|
201
|
+
h('div', {
|
|
202
|
+
className: 'dim-brand',
|
|
203
|
+
tabIndex: 0,
|
|
204
|
+
'aria-describedby': versionTooltipId,
|
|
205
|
+
},
|
|
199
206
|
h('strong', { className: 'dim-brandName' }, 'DSH-IM'),
|
|
200
|
-
h('p', null, '让 DeepSeek Harness 触手可及')
|
|
207
|
+
h('p', null, '让 DeepSeek Harness 触手可及'),
|
|
208
|
+
h('span', {
|
|
209
|
+
id: versionTooltipId,
|
|
210
|
+
className: 'dim-versionTooltip',
|
|
211
|
+
role: 'tooltip',
|
|
212
|
+
},
|
|
213
|
+
h('span', null, '当前版本'),
|
|
214
|
+
h('strong', null, `v${IM_PLUGIN_VERSION}`))),
|
|
201
215
|
h('span', { className: 'dim-githubAction' },
|
|
202
216
|
h('a', {
|
|
203
217
|
className: 'dim-githubLink',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function text(value, maxLength) {
|
|
2
|
+
if (typeof value !== 'string') return null;
|
|
3
|
+
const trimmed = value.trim();
|
|
4
|
+
return trimmed ? trimmed.slice(0, maxLength) : null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeLastMessageError(value) {
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
9
|
+
const code = text(value.code, 64);
|
|
10
|
+
const reason = text(value.reason, 64);
|
|
11
|
+
const message = text(value.message, 500);
|
|
12
|
+
const referenceId = text(value.referenceId, 40);
|
|
13
|
+
const at = Number.isFinite(value.at) ? value.at : null;
|
|
14
|
+
return code && reason && message && referenceId && at !== null
|
|
15
|
+
? { code, reason, message, referenceId, at }
|
|
16
|
+
: null;
|
|
17
|
+
}
|
|
@@ -12,9 +12,13 @@ const CSS = String.raw`
|
|
|
12
12
|
}
|
|
13
13
|
.dim-page *, .dim-page *::before, .dim-page *::after { box-sizing: border-box; }
|
|
14
14
|
.dim-title { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 0 0 18px; }
|
|
15
|
-
.dim-brand { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 1px; }
|
|
15
|
+
.dim-brand { position: relative; min-width: 0; width: max-content; max-width: 100%; display: flex; flex-direction: column; align-items: flex-start; gap: 1px; margin: -2px -6px; padding: 2px 6px; border-radius: 8px; cursor: help; }
|
|
16
|
+
.dim-brand:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 70%, white); outline-offset: 2px; }
|
|
16
17
|
.dim-brandName { color: var(--dsw-alias-label-primary, #1f2329); font-size: 20px; line-height: 24px; font-weight: 800; letter-spacing: .04em; }
|
|
17
18
|
.dim-title p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; font-weight: 500; white-space: nowrap; }
|
|
19
|
+
.dim-versionTooltip { position: absolute; left: 0; bottom: calc(100% + 8px); z-index: 20; width: max-content; max-width: min(220px, 80vw); display: inline-flex; align-items: center; gap: 6px; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
|
|
20
|
+
.dim-versionTooltip strong { color: var(--dsw-alias-label-primary, #1f2329); font: 600 11px/16px ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
21
|
+
.dim-brand:hover .dim-versionTooltip, .dim-brand:focus .dim-versionTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
|
|
18
22
|
.dim-githubAction { position: relative; display: inline-flex; flex: none; }
|
|
19
23
|
.dim-githubLink { min-height: 30px; display: inline-flex; align-items: center; gap: 5px; flex: none; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 12px; line-height: normal; font-weight: 560; text-decoration: none; transition: border-color .15s ease, color .15s ease, background .15s ease; }
|
|
20
24
|
.dim-githubLink:hover { border-color: #aeb3bb; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
normalizeAgentPresetId,
|
|
5
5
|
} from '../../../../src/channels/shared/agent-preset.mjs';
|
|
6
6
|
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
|
7
|
+
import { publicMessageFailure } from '../../../../src/channels/shared/message-failure.mjs';
|
|
7
8
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
8
9
|
import { publicWorkspaceError, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
|
9
10
|
import { validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
|
@@ -281,6 +282,8 @@ function publicBotEntry(entry) {
|
|
|
281
282
|
health: publicHealth(source, connected),
|
|
282
283
|
};
|
|
283
284
|
if (typeof source.workspace === 'string' && source.workspace) result.workspace = source.workspace;
|
|
285
|
+
const lastMessageError = publicMessageFailure(source.lastMessageError);
|
|
286
|
+
if (lastMessageError) result.lastMessageError = lastMessageError;
|
|
284
287
|
const error = publicError(source.error);
|
|
285
288
|
if (error) result.error = error;
|
|
286
289
|
return result;
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
} from '../shared/batch-input.mjs';
|
|
33
33
|
import {
|
|
34
34
|
hasInboundImages,
|
|
35
|
+
imagePromptDiagnostic,
|
|
35
36
|
imagePromptUserMessage,
|
|
36
37
|
promptContentForMessage,
|
|
37
38
|
} from '../shared/image-prompt.mjs';
|
|
@@ -46,10 +47,15 @@ import {
|
|
|
46
47
|
createDeliveryReceipt,
|
|
47
48
|
providerMessageIdsFor,
|
|
48
49
|
} from '../shared/semantic/delivery.mjs';
|
|
50
|
+
import {
|
|
51
|
+
channelDeliveryFailure,
|
|
52
|
+
clearLastMessageFailure,
|
|
53
|
+
messageFailureText,
|
|
54
|
+
setLastMessageFailure,
|
|
55
|
+
} from '../shared/message-failure.mjs';
|
|
49
56
|
import { t } from '../shared/i18n.mjs';
|
|
50
57
|
|
|
51
58
|
const CARD_INITIAL_TEXT = '已连接 DeepSeek Harness,正在思考…';
|
|
52
|
-
const CARD_ERROR_TEXT = '消息处理失败,请稍后重试。';
|
|
53
59
|
const INTERACTION_RESOLVED_TEXT = '这个问题已在其他客户端处理,无需再次回答。';
|
|
54
60
|
|
|
55
61
|
const HELP_TEXT_LINES = [
|
|
@@ -77,6 +83,7 @@ const HELP_TEXT_LINES = [
|
|
|
77
83
|
'/send 提交当前批次',
|
|
78
84
|
'/cancel 取消当前批次',
|
|
79
85
|
'/status 检查连接状态',
|
|
86
|
+
'/version 查看插件版本',
|
|
80
87
|
'/help 显示本帮助',
|
|
81
88
|
];
|
|
82
89
|
|
|
@@ -325,6 +332,7 @@ export function createDingtalkBridgeStatus({ pendingSenders = [] } = {}) {
|
|
|
325
332
|
lastReplyAt: null,
|
|
326
333
|
lastRejectedAt: null,
|
|
327
334
|
lastError: null,
|
|
335
|
+
lastMessageError: null,
|
|
328
336
|
pendingSenders: structuredClone(pendingSenders),
|
|
329
337
|
stats: {
|
|
330
338
|
messagesReceived: 0,
|
|
@@ -483,9 +491,13 @@ export class DingtalkHarnessBridge {
|
|
|
483
491
|
commandRunner,
|
|
484
492
|
).catch((error) => {
|
|
485
493
|
if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
|
|
486
|
-
this.#status.lastError =
|
|
487
|
-
|
|
488
|
-
|
|
494
|
+
this.#status.lastError = error?.message ?? String(error);
|
|
495
|
+
const failure = setLastMessageFailure(this.#status, error);
|
|
496
|
+
this.#logger.error?.(
|
|
497
|
+
`[dsh-dingtalk] failed to process a command [${failure.referenceId}]`,
|
|
498
|
+
safeErrorDiagnostic(error),
|
|
499
|
+
);
|
|
500
|
+
return this.#send(sessionWebhook, messageFailureText(failure)).catch(() => undefined);
|
|
489
501
|
}).finally(() => {
|
|
490
502
|
this.#acceptedMessageIds.delete(messageId);
|
|
491
503
|
this.#commandTasks.delete(task);
|
|
@@ -658,9 +670,13 @@ export class DingtalkHarnessBridge {
|
|
|
658
670
|
this.#status.lastError = null;
|
|
659
671
|
}).catch(async (error) => {
|
|
660
672
|
if (this.#signal?.aborted) return;
|
|
661
|
-
this.#status.lastError =
|
|
662
|
-
|
|
663
|
-
|
|
673
|
+
this.#status.lastError = error?.message ?? String(error);
|
|
674
|
+
const failure = setLastMessageFailure(this.#status, error);
|
|
675
|
+
this.#logger.error?.(
|
|
676
|
+
`[dsh-dingtalk] failed to process a batch input message [${failure.referenceId}]`,
|
|
677
|
+
safeErrorDiagnostic(error),
|
|
678
|
+
);
|
|
679
|
+
await this.#send(sessionWebhook, messageFailureText(failure)).catch(() => undefined);
|
|
664
680
|
}).finally(() => {
|
|
665
681
|
this.#acceptedMessageIds.delete(messageId);
|
|
666
682
|
this.#commandTasks.delete(task);
|
|
@@ -818,7 +834,7 @@ export class DingtalkHarnessBridge {
|
|
|
818
834
|
});
|
|
819
835
|
}
|
|
820
836
|
} catch (error) {
|
|
821
|
-
textDeliveryError = error;
|
|
837
|
+
textDeliveryError = channelDeliveryFailure(error);
|
|
822
838
|
}
|
|
823
839
|
const delivery = await this.#deliverArtifacts(
|
|
824
840
|
fileTarget(message, sender, this.#clientId),
|
|
@@ -828,9 +844,15 @@ export class DingtalkHarnessBridge {
|
|
|
828
844
|
textReceipt,
|
|
829
845
|
);
|
|
830
846
|
if (textDeliveryError && !delivery.userVisible) throw textDeliveryError;
|
|
847
|
+
if (textDeliveryError && delivery.artifactSendErrors === 0) {
|
|
848
|
+
setLastMessageFailure(this.#status, textDeliveryError);
|
|
849
|
+
}
|
|
831
850
|
increment(this.#status, 'messagesReplied');
|
|
832
851
|
this.#status.lastReplyAt = new Date().toISOString();
|
|
833
852
|
this.#status.lastError = null;
|
|
853
|
+
if (!textDeliveryError && delivery.artifactSendErrors === 0) {
|
|
854
|
+
clearLastMessageFailure(this.#status);
|
|
855
|
+
}
|
|
834
856
|
return delivery.receipt;
|
|
835
857
|
} catch (error) {
|
|
836
858
|
let batchFailureMessage = null;
|
|
@@ -847,15 +869,19 @@ export class DingtalkHarnessBridge {
|
|
|
847
869
|
return;
|
|
848
870
|
}
|
|
849
871
|
if (this.#signal?.aborted) return;
|
|
850
|
-
this.#status.lastError =
|
|
872
|
+
this.#status.lastError = error?.message ?? String(error);
|
|
873
|
+
const userMessage = inboundFileUserMessage(error)
|
|
874
|
+
?? dingtalkImageErrorUserMessage(error);
|
|
875
|
+
const failure = setLastMessageFailure(this.#status, error, {
|
|
876
|
+
userMessage,
|
|
877
|
+
reason: imagePromptDiagnostic(error)?.reason,
|
|
878
|
+
});
|
|
851
879
|
this.#logger.error?.(
|
|
852
|
-
|
|
880
|
+
`[dsh-dingtalk] failed to process an inbound message [${failure.referenceId}]`,
|
|
853
881
|
safeErrorDiagnostic(error),
|
|
854
882
|
);
|
|
855
883
|
try {
|
|
856
|
-
const errorText =
|
|
857
|
-
?? dingtalkImageErrorUserMessage(error)
|
|
858
|
-
?? t(CARD_ERROR_TEXT);
|
|
884
|
+
const errorText = messageFailureText(failure);
|
|
859
885
|
const visibleError = batchFailureMessage
|
|
860
886
|
? `${errorText}\n\n${batchFailureMessage}`
|
|
861
887
|
: errorText;
|
|
@@ -1200,9 +1226,13 @@ export class DingtalkHarnessBridge {
|
|
|
1200
1226
|
sendFile: typeof this.#api.sendFile === 'function'
|
|
1201
1227
|
? (file) => sendArtifact('sendFile', file)
|
|
1202
1228
|
: undefined,
|
|
1203
|
-
|
|
1229
|
+
onFailure: (artifact, error) => setLastMessageFailure(this.#status, error, {
|
|
1230
|
+
userMessage: artifactFailureText(artifact?.fileName, error),
|
|
1231
|
+
reason: error?.code,
|
|
1232
|
+
}),
|
|
1233
|
+
sendFailureNotice: (_artifact, _error, failure) => this.#send(
|
|
1204
1234
|
sessionWebhook,
|
|
1205
|
-
|
|
1235
|
+
messageFailureText(failure),
|
|
1206
1236
|
),
|
|
1207
1237
|
logger: this.#logger,
|
|
1208
1238
|
});
|
|
@@ -1210,7 +1240,11 @@ export class DingtalkHarnessBridge {
|
|
|
1210
1240
|
+ delivery.artifactsSent;
|
|
1211
1241
|
this.#status.artifactSendErrors = (this.#status.artifactSendErrors ?? 0)
|
|
1212
1242
|
+ delivery.artifactSendErrors;
|
|
1213
|
-
return {
|
|
1243
|
+
return {
|
|
1244
|
+
receipt: delivery.receipt,
|
|
1245
|
+
userVisible: delivery.userVisible,
|
|
1246
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
1247
|
+
};
|
|
1214
1248
|
}
|
|
1215
1249
|
}
|
|
1216
1250
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t } from '../shared/i18n.mjs';
|
|
2
2
|
|
|
3
3
|
const DEFAULT_UPDATE_INTERVAL_MS = 500;
|
|
4
|
-
const
|
|
4
|
+
const CLOSED_TEXT = '卡片已结束,请查看后续消息。';
|
|
5
5
|
|
|
6
6
|
function requiredText(value, name) {
|
|
7
7
|
if (typeof value !== 'string') throw new TypeError(`${name} must be a string`);
|
|
@@ -102,7 +102,7 @@ export function createDingTalkCardStream({
|
|
|
102
102
|
if (!cleanupPromise) {
|
|
103
103
|
cleanupPromise = api.failAiCard({
|
|
104
104
|
...cardRequest,
|
|
105
|
-
text: t(
|
|
105
|
+
text: t(CLOSED_TEXT),
|
|
106
106
|
signal: AbortSignal.timeout(5_000),
|
|
107
107
|
}).then(
|
|
108
108
|
() => true,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
connectionTestTargetUnavailable,
|
|
12
12
|
} from '../shared/connection-test.mjs';
|
|
13
13
|
import { t } from '../shared/i18n.mjs';
|
|
14
|
+
import { publicMessageFailure } from '../shared/message-failure.mjs';
|
|
14
15
|
|
|
15
16
|
const ACTIVE_ATTEMPT_STATES = new Set(['starting', 'pending', 'connecting']);
|
|
16
17
|
const TERMINAL_ATTEMPT_STATES = new Set(['connected', 'expired', 'failed', 'cancelled']);
|
|
@@ -516,6 +517,7 @@ export class DingtalkController {
|
|
|
516
517
|
messagesReceived: Number(currentStatus.messagesReceived) || 0,
|
|
517
518
|
messagesReplied: Number(currentStatus.messagesReplied) || 0,
|
|
518
519
|
},
|
|
520
|
+
lastMessageError: publicMessageFailure(currentStatus.lastMessageError),
|
|
519
521
|
senders: {
|
|
520
522
|
pending,
|
|
521
523
|
approved: config.approvedSenders.map(publicApprovedSender),
|