@xmanrui/dsh-im 2.4.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 +1 -1
- package/README.md +1 -1
- package/lib/client.js +321 -109
- package/lib/index.js +214 -207
- 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 +9 -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 +4 -0
- 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 +49 -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 +99 -43
- package/src/channels/feishu/multi-bot-controller.mjs +2 -0
- package/src/channels/qq/qq-bridge.mjs +83 -28
- package/src/channels/qq/qq-controller.mjs +2 -0
- package/src/channels/shared/harness-client.mjs +40 -4
- package/src/channels/shared/i18n-en/shared-a.mjs +77 -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 +65 -60
- package/src/channels/shared/token-bot-controller.mjs +2 -0
- package/src/channels/slack/slack-controller.mjs +2 -0
- package/src/channels/wecom/wecom-bridge.mjs +49 -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 +261 -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
package/package.json
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
import { h } from './i18n.js';
|
|
3
|
+
import { h, isEnglish } from './i18n.js';
|
|
4
|
+
|
|
5
|
+
function messageErrorTime(value) {
|
|
6
|
+
try {
|
|
7
|
+
return new Intl.DateTimeFormat(isEnglish() ? 'en-US' : 'zh-CN', {
|
|
8
|
+
year: 'numeric',
|
|
9
|
+
month: '2-digit',
|
|
10
|
+
day: '2-digit',
|
|
11
|
+
hour: '2-digit',
|
|
12
|
+
minute: '2-digit',
|
|
13
|
+
}).format(new Date(value));
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
4
18
|
|
|
5
19
|
export function ChannelListHeading({ className = '', id, title, connectionLabel }) {
|
|
6
20
|
const helpId = React.useId();
|
|
@@ -46,3 +60,24 @@ export function BotStatusMeta({
|
|
|
46
60
|
h('span', null, '最近检查'),
|
|
47
61
|
h('span', null, formatCheckedTime(lastCheckedAt))));
|
|
48
62
|
}
|
|
63
|
+
|
|
64
|
+
export function LastMessageErrorSummary({ className = '', error }) {
|
|
65
|
+
if (!error) return null;
|
|
66
|
+
const occurredAt = messageErrorTime(error.at);
|
|
67
|
+
return h('div', {
|
|
68
|
+
className: `${className} dim-cardSummary`.trim(),
|
|
69
|
+
role: 'status',
|
|
70
|
+
},
|
|
71
|
+
h('strong', null, '最近一条消息处理失败'),
|
|
72
|
+
':',
|
|
73
|
+
h('span', null, error.message),
|
|
74
|
+
'(',
|
|
75
|
+
h('span', null, '错误码'),
|
|
76
|
+
` ${error.code} · `,
|
|
77
|
+
h('span', null, '参考号'),
|
|
78
|
+
` ${error.referenceId}`,
|
|
79
|
+
occurredAt ? h(React.Fragment, null,
|
|
80
|
+
' · ',
|
|
81
|
+
h('time', { dateTime: new Date(error.at).toISOString() }, occurredAt)) : null,
|
|
82
|
+
')');
|
|
83
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
2
|
+
import { normalizeLastMessageError } from '../../last-message-error.js';
|
|
2
3
|
|
|
3
4
|
export const DINGTALK_RPC_CHANNEL = '/dingtalk';
|
|
4
5
|
|
|
@@ -178,6 +179,7 @@ function normalizeBot(value) {
|
|
|
178
179
|
messagesReceived: nonNegativeInteger(stats.messagesReceived),
|
|
179
180
|
messagesReplied: nonNegativeInteger(stats.messagesReplied),
|
|
180
181
|
},
|
|
182
|
+
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
181
183
|
error: normalizeError(value.error, 'DINGTALK_ACCOUNT_ERROR', '钉钉连接尚未就绪') ?? null,
|
|
182
184
|
};
|
|
183
185
|
}
|
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
EMPTY_AGENT_PRESET_CATALOG,
|
|
10
10
|
} from '../../agent-preset.js';
|
|
11
11
|
import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
BotStatusMeta,
|
|
14
|
+
ChannelListHeading,
|
|
15
|
+
LastMessageErrorSummary,
|
|
16
|
+
} from '../../channel-card-meta.js';
|
|
13
17
|
import {
|
|
14
18
|
DINGTALK_ENDPOINTS,
|
|
15
19
|
DINGTALK_RPC_CHANNEL,
|
|
@@ -262,6 +266,10 @@ export function AccountCard({
|
|
|
262
266
|
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) },
|
|
263
267
|
'移除接入')),
|
|
264
268
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
269
|
+
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
270
|
+
className: 'ddt-summary',
|
|
271
|
+
error: account.lastMessageError,
|
|
272
|
+
}) : null,
|
|
265
273
|
feedback ? h('div', {
|
|
266
274
|
className: 'ddt-summary dim-cardFeedback',
|
|
267
275
|
role: 'status',
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId } from "../../agent-preset.js";
|
|
10
|
+
import { normalizeLastMessageError } from "../../last-message-error.js";
|
|
10
11
|
|
|
11
12
|
export const FEISHU_RPC_CHANNEL = "/feishu";
|
|
12
13
|
|
|
@@ -206,6 +207,7 @@ export function normalizeBotConnection(value, fallbackBotId) {
|
|
|
206
207
|
groupMessagePermissionGranted: value.groupMessagePermissionGranted === true,
|
|
207
208
|
bot: normalizeBot(value.bot),
|
|
208
209
|
health: normalizeHealth(value.health, connected),
|
|
210
|
+
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
209
211
|
error: normalizeError(value.error),
|
|
210
212
|
};
|
|
211
213
|
}
|
|
@@ -226,6 +228,7 @@ export function normalizeBotsSnapshot(value) {
|
|
|
226
228
|
configured: true,
|
|
227
229
|
bot: value.bot,
|
|
228
230
|
health: value.health,
|
|
231
|
+
lastMessageError: value.lastMessageError,
|
|
229
232
|
error: value.error,
|
|
230
233
|
}];
|
|
231
234
|
}
|
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
EMPTY_AGENT_PRESET_CATALOG,
|
|
24
24
|
} from "../../agent-preset.js";
|
|
25
25
|
import { useWorkspaceSnapshotFence } from "../../workspace-snapshot-fence.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
BotStatusMeta,
|
|
28
|
+
ChannelListHeading,
|
|
29
|
+
LastMessageErrorSummary,
|
|
30
|
+
} from "../../channel-card-meta.js";
|
|
27
31
|
import { installFeishuStyles } from "./styles.js";
|
|
28
32
|
|
|
29
33
|
export const name = "feishu-settings";
|
|
@@ -661,6 +665,10 @@ export function BotCard({
|
|
|
661
665
|
}, "移除接入")),
|
|
662
666
|
summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined },
|
|
663
667
|
summary) : null,
|
|
668
|
+
connection.lastMessageError ? h(LastMessageErrorSummary, {
|
|
669
|
+
className: "bxf-healthSummary",
|
|
670
|
+
error: connection.lastMessageError,
|
|
671
|
+
}) : null,
|
|
664
672
|
testNotice ? h("div", {
|
|
665
673
|
className: "bxf-healthSummary dim-cardFeedback",
|
|
666
674
|
role: "status",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
2
|
+
import { normalizeLastMessageError } from '../../last-message-error.js';
|
|
2
3
|
|
|
3
4
|
export const QQ_RPC_CHANNEL = '/qq';
|
|
4
5
|
|
|
@@ -94,6 +95,7 @@ function normalizeBot(value) {
|
|
|
94
95
|
summary: text(value.health?.summary, connected ? 'QQ WebSocket 长连接运行正常' : 'QQ 连接尚未就绪'),
|
|
95
96
|
lastCheckedAt: timestamp(value.health?.lastCheckedAt),
|
|
96
97
|
},
|
|
98
|
+
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
97
99
|
error: isRecord(value.error) ? {
|
|
98
100
|
code: text(value.error.code, 'QQ_ACCOUNT_ERROR', 80),
|
|
99
101
|
message: text(value.error.message, 'QQ 连接尚未就绪'),
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
EMPTY_AGENT_PRESET_CATALOG,
|
|
11
11
|
} from '../../agent-preset.js';
|
|
12
12
|
import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
BotStatusMeta,
|
|
15
|
+
ChannelListHeading,
|
|
16
|
+
LastMessageErrorSummary,
|
|
17
|
+
} from '../../channel-card-meta.js';
|
|
14
18
|
import { installDingtalkStyles } from '../dingtalk/styles.js';
|
|
15
19
|
import {
|
|
16
20
|
QQ_ENDPOINTS,
|
|
@@ -201,6 +205,10 @@ export function AccountCard({
|
|
|
201
205
|
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
|
202
206
|
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
|
|
203
207
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
208
|
+
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
209
|
+
className: 'ddt-summary',
|
|
210
|
+
error: account.lastMessageError,
|
|
211
|
+
}) : null,
|
|
204
212
|
feedback ? h('div', {
|
|
205
213
|
className: 'ddt-summary dim-cardFeedback',
|
|
206
214
|
role: 'status',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
2
|
+
import { normalizeLastMessageError } from '../../last-message-error.js';
|
|
2
3
|
|
|
3
4
|
const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
|
|
4
5
|
|
|
@@ -68,6 +69,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
68
69
|
),
|
|
69
70
|
lastCheckedAt: timestamp(value.health?.lastCheckedAt),
|
|
70
71
|
},
|
|
72
|
+
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
71
73
|
error: isRecord(value.error) ? {
|
|
72
74
|
code: text(value.error.code, `${channel.toUpperCase()}_ACCOUNT_ERROR`, 80),
|
|
73
75
|
message: text(value.error.message, `${channel}连接尚未就绪`),
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
EMPTY_AGENT_PRESET_CATALOG,
|
|
11
11
|
} from '../../agent-preset.js';
|
|
12
12
|
import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
BotStatusMeta,
|
|
15
|
+
ChannelListHeading,
|
|
16
|
+
LastMessageErrorSummary,
|
|
17
|
+
} from '../../channel-card-meta.js';
|
|
14
18
|
|
|
15
19
|
const Button = React.forwardRef(function Button(
|
|
16
20
|
{ children, kind = 'secondary', className = '', ...props },
|
|
@@ -121,6 +125,10 @@ export function createTokenChannelSettings(definition) {
|
|
|
121
125
|
disabled: Boolean(busy),
|
|
122
126
|
}, '移除接入')),
|
|
123
127
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
128
|
+
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
129
|
+
className: 'ddt-summary',
|
|
130
|
+
error: account.lastMessageError,
|
|
131
|
+
}) : null,
|
|
124
132
|
testNotice ? h('div', {
|
|
125
133
|
className: 'ddt-summary dim-cardFeedback',
|
|
126
134
|
role: 'status',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
2
|
+
import { normalizeLastMessageError } from '../../last-message-error.js';
|
|
2
3
|
|
|
3
4
|
export const WECOM_RPC_CHANNEL = '/wecom';
|
|
4
5
|
|
|
@@ -103,6 +104,7 @@ function normalizeBot(value) {
|
|
|
103
104
|
summary: text(value.health?.summary, connected ? '企业微信 WebSocket 长连接运行正常' : '企业微信连接尚未就绪'),
|
|
104
105
|
lastCheckedAt: timestamp(value.health?.lastCheckedAt),
|
|
105
106
|
},
|
|
107
|
+
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
106
108
|
error: isRecord(value.error) ? {
|
|
107
109
|
code: text(value.error.code, 'WECOM_ACCOUNT_ERROR', 80),
|
|
108
110
|
message: text(value.error.message, '企业微信连接尚未就绪'),
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
EMPTY_AGENT_PRESET_CATALOG,
|
|
11
11
|
} from '../../agent-preset.js';
|
|
12
12
|
import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
BotStatusMeta,
|
|
15
|
+
ChannelListHeading,
|
|
16
|
+
LastMessageErrorSummary,
|
|
17
|
+
} from '../../channel-card-meta.js';
|
|
14
18
|
import { installDingtalkStyles } from '../dingtalk/styles.js';
|
|
15
19
|
import {
|
|
16
20
|
WECOM_ENDPOINTS,
|
|
@@ -200,6 +204,10 @@ export function AccountCard({
|
|
|
200
204
|
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
|
201
205
|
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
|
|
202
206
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
207
|
+
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
208
|
+
className: 'ddt-summary',
|
|
209
|
+
error: account.lastMessageError,
|
|
210
|
+
}) : null,
|
|
203
211
|
feedback ? h('div', {
|
|
204
212
|
className: 'ddt-summary dim-cardFeedback',
|
|
205
213
|
role: 'status',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
2
|
+
import { normalizeLastMessageError } from '../../last-message-error.js';
|
|
2
3
|
|
|
3
4
|
export const WEIXIN_RPC_CHANNEL = '/weixin';
|
|
4
5
|
export const WEIXIN_ENDPOINTS = Object.freeze({
|
|
@@ -48,15 +49,6 @@ function normalizeTestMessage(value) {
|
|
|
48
49
|
return { sent: false, code };
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
function normalizeMessageError(value) {
|
|
52
|
-
if (!isRecord(value)) return null;
|
|
53
|
-
const code = string(value.code).slice(0, 64);
|
|
54
|
-
const reason = string(value.reason).slice(0, 128);
|
|
55
|
-
const message = string(value.message).slice(0, 500);
|
|
56
|
-
const at = timestamp(value.at);
|
|
57
|
-
return code && reason && message && at !== null ? { code, reason, message, at } : null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
52
|
export function unwrapRpcResult(result) {
|
|
61
53
|
if (!isRecord(result) || typeof result.ok !== 'boolean') {
|
|
62
54
|
throw new Error('微信服务返回了无法识别的响应');
|
|
@@ -141,7 +133,7 @@ function normalizeBot(value) {
|
|
|
141
133
|
messagesReceived: Math.max(0, Number(value.stats?.messagesReceived) || 0),
|
|
142
134
|
messagesReplied: Math.max(0, Number(value.stats?.messagesReplied) || 0),
|
|
143
135
|
},
|
|
144
|
-
lastMessageError:
|
|
136
|
+
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
145
137
|
error: isRecord(value.error)
|
|
146
138
|
? {
|
|
147
139
|
code: string(value.error.code, 'WEIXIN_ACCOUNT_ERROR'),
|
|
@@ -22,7 +22,11 @@ import {
|
|
|
22
22
|
EMPTY_AGENT_PRESET_CATALOG,
|
|
23
23
|
} from '../../agent-preset.js';
|
|
24
24
|
import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
BotStatusMeta,
|
|
27
|
+
ChannelListHeading,
|
|
28
|
+
LastMessageErrorSummary,
|
|
29
|
+
} from '../../channel-card-meta.js';
|
|
26
30
|
import { installWeixinStyles } from './styles.js';
|
|
27
31
|
|
|
28
32
|
export const name = 'weixin-settings';
|
|
@@ -247,10 +251,10 @@ export function AccountCard({
|
|
|
247
251
|
busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
|
248
252
|
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
|
|
249
253
|
summary ? h('div', { className: 'dxw-summary dim-cardSummary' }, summary) : null,
|
|
250
|
-
account.lastMessageError ? h(
|
|
251
|
-
className: 'dxw-summary
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
+
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
255
|
+
className: 'dxw-summary',
|
|
256
|
+
error: account.lastMessageError,
|
|
257
|
+
}) : null,
|
|
254
258
|
feedback ? h('div', {
|
|
255
259
|
className: 'dxw-summary dim-cardFeedback',
|
|
256
260
|
role: 'status',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
2
|
+
import { normalizeLastMessageError } from '../../last-message-error.js';
|
|
2
3
|
|
|
3
4
|
export const WHATSAPP_RPC_CHANNEL = '/whatsapp';
|
|
4
5
|
|
|
@@ -106,6 +107,7 @@ function normalizeBot(value) {
|
|
|
106
107
|
? 'WhatsApp Web 关联设备运行正常' : 'WhatsApp 连接尚未就绪'),
|
|
107
108
|
lastCheckedAt: timestamp(value.health?.lastCheckedAt),
|
|
108
109
|
},
|
|
110
|
+
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
109
111
|
error: isRecord(value.error) ? {
|
|
110
112
|
code: text(value.error.code, 'WHATSAPP_ACCOUNT_ERROR', 80),
|
|
111
113
|
message: text(value.error.message, 'WhatsApp 连接尚未就绪'),
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
EMPTY_AGENT_PRESET_CATALOG,
|
|
11
11
|
} from '../../agent-preset.js';
|
|
12
12
|
import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
BotStatusMeta,
|
|
15
|
+
ChannelListHeading,
|
|
16
|
+
LastMessageErrorSummary,
|
|
17
|
+
} from '../../channel-card-meta.js';
|
|
14
18
|
import { installDingtalkStyles } from '../dingtalk/styles.js';
|
|
15
19
|
import {
|
|
16
20
|
WHATSAPP_ENDPOINTS,
|
|
@@ -345,6 +349,10 @@ export function WhatsappAccountCard({
|
|
|
345
349
|
className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy),
|
|
346
350
|
}, '移除接入')),
|
|
347
351
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
352
|
+
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
353
|
+
className: 'ddt-summary',
|
|
354
|
+
error: account.lastMessageError,
|
|
355
|
+
}) : null,
|
|
348
356
|
testNotice ? h('div', {
|
|
349
357
|
className: 'ddt-summary dim-cardFeedback',
|
|
350
358
|
role: 'status',
|
|
@@ -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',
|
|
@@ -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 = [
|
|
@@ -326,6 +332,7 @@ export function createDingtalkBridgeStatus({ pendingSenders = [] } = {}) {
|
|
|
326
332
|
lastReplyAt: null,
|
|
327
333
|
lastRejectedAt: null,
|
|
328
334
|
lastError: null,
|
|
335
|
+
lastMessageError: null,
|
|
329
336
|
pendingSenders: structuredClone(pendingSenders),
|
|
330
337
|
stats: {
|
|
331
338
|
messagesReceived: 0,
|
|
@@ -484,9 +491,13 @@ export class DingtalkHarnessBridge {
|
|
|
484
491
|
commandRunner,
|
|
485
492
|
).catch((error) => {
|
|
486
493
|
if (error?.code === 'turn-stopped' || this.#signal?.aborted) return;
|
|
487
|
-
this.#status.lastError =
|
|
488
|
-
|
|
489
|
-
|
|
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);
|
|
490
501
|
}).finally(() => {
|
|
491
502
|
this.#acceptedMessageIds.delete(messageId);
|
|
492
503
|
this.#commandTasks.delete(task);
|
|
@@ -659,9 +670,13 @@ export class DingtalkHarnessBridge {
|
|
|
659
670
|
this.#status.lastError = null;
|
|
660
671
|
}).catch(async (error) => {
|
|
661
672
|
if (this.#signal?.aborted) return;
|
|
662
|
-
this.#status.lastError =
|
|
663
|
-
|
|
664
|
-
|
|
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);
|
|
665
680
|
}).finally(() => {
|
|
666
681
|
this.#acceptedMessageIds.delete(messageId);
|
|
667
682
|
this.#commandTasks.delete(task);
|
|
@@ -819,7 +834,7 @@ export class DingtalkHarnessBridge {
|
|
|
819
834
|
});
|
|
820
835
|
}
|
|
821
836
|
} catch (error) {
|
|
822
|
-
textDeliveryError = error;
|
|
837
|
+
textDeliveryError = channelDeliveryFailure(error);
|
|
823
838
|
}
|
|
824
839
|
const delivery = await this.#deliverArtifacts(
|
|
825
840
|
fileTarget(message, sender, this.#clientId),
|
|
@@ -829,9 +844,15 @@ export class DingtalkHarnessBridge {
|
|
|
829
844
|
textReceipt,
|
|
830
845
|
);
|
|
831
846
|
if (textDeliveryError && !delivery.userVisible) throw textDeliveryError;
|
|
847
|
+
if (textDeliveryError && delivery.artifactSendErrors === 0) {
|
|
848
|
+
setLastMessageFailure(this.#status, textDeliveryError);
|
|
849
|
+
}
|
|
832
850
|
increment(this.#status, 'messagesReplied');
|
|
833
851
|
this.#status.lastReplyAt = new Date().toISOString();
|
|
834
852
|
this.#status.lastError = null;
|
|
853
|
+
if (!textDeliveryError && delivery.artifactSendErrors === 0) {
|
|
854
|
+
clearLastMessageFailure(this.#status);
|
|
855
|
+
}
|
|
835
856
|
return delivery.receipt;
|
|
836
857
|
} catch (error) {
|
|
837
858
|
let batchFailureMessage = null;
|
|
@@ -848,15 +869,19 @@ export class DingtalkHarnessBridge {
|
|
|
848
869
|
return;
|
|
849
870
|
}
|
|
850
871
|
if (this.#signal?.aborted) return;
|
|
851
|
-
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
|
+
});
|
|
852
879
|
this.#logger.error?.(
|
|
853
|
-
|
|
880
|
+
`[dsh-dingtalk] failed to process an inbound message [${failure.referenceId}]`,
|
|
854
881
|
safeErrorDiagnostic(error),
|
|
855
882
|
);
|
|
856
883
|
try {
|
|
857
|
-
const errorText =
|
|
858
|
-
?? dingtalkImageErrorUserMessage(error)
|
|
859
|
-
?? t(CARD_ERROR_TEXT);
|
|
884
|
+
const errorText = messageFailureText(failure);
|
|
860
885
|
const visibleError = batchFailureMessage
|
|
861
886
|
? `${errorText}\n\n${batchFailureMessage}`
|
|
862
887
|
: errorText;
|
|
@@ -1201,9 +1226,13 @@ export class DingtalkHarnessBridge {
|
|
|
1201
1226
|
sendFile: typeof this.#api.sendFile === 'function'
|
|
1202
1227
|
? (file) => sendArtifact('sendFile', file)
|
|
1203
1228
|
: undefined,
|
|
1204
|
-
|
|
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(
|
|
1205
1234
|
sessionWebhook,
|
|
1206
|
-
|
|
1235
|
+
messageFailureText(failure),
|
|
1207
1236
|
),
|
|
1208
1237
|
logger: this.#logger,
|
|
1209
1238
|
});
|
|
@@ -1211,7 +1240,11 @@ export class DingtalkHarnessBridge {
|
|
|
1211
1240
|
+ delivery.artifactsSent;
|
|
1212
1241
|
this.#status.artifactSendErrors = (this.#status.artifactSendErrors ?? 0)
|
|
1213
1242
|
+ delivery.artifactSendErrors;
|
|
1214
|
-
return {
|
|
1243
|
+
return {
|
|
1244
|
+
receipt: delivery.receipt,
|
|
1245
|
+
userVisible: delivery.userVisible,
|
|
1246
|
+
artifactSendErrors: delivery.artifactSendErrors,
|
|
1247
|
+
};
|
|
1215
1248
|
}
|
|
1216
1249
|
}
|
|
1217
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,
|