@xmanrui/dsh-im 2.4.0 → 2.6.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 +2 -2
- package/README.md +2 -2
- package/lib/client.js +322 -109
- package/lib/index.js +218 -211
- 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 +102 -6
- package/src/channels/dingtalk/dingtalk-bridge.mjs +247 -38
- package/src/channels/dingtalk/dingtalk-card-stream.mjs +2 -2
- package/src/channels/dingtalk/dingtalk-controller.mjs +2 -0
- package/src/channels/discord/discord-api.mjs +35 -0
- package/src/channels/discord/discord-bridge.mjs +1 -0
- package/src/channels/discord/discord-runtime.mjs +26 -0
- package/src/channels/feishu/bridge.mjs +115 -69
- 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/status-reaction.mjs +107 -0
- package/src/channels/shared/text-harness-bridge.mjs +123 -66
- package/src/channels/shared/token-bot-controller.mjs +2 -0
- package/src/channels/slack/manifest.mjs +1 -0
- package/src/channels/slack/slack-api.mjs +28 -0
- package/src/channels/slack/slack-bridge.mjs +5 -0
- package/src/channels/slack/slack-controller.mjs +2 -0
- package/src/channels/slack/slack-runtime.mjs +24 -0
- package/src/channels/telegram/telegram-api.mjs +9 -0
- package/src/channels/telegram/telegram-bridge.mjs +1 -0
- package/src/channels/telegram/telegram-runtime.mjs +20 -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-bridge.mjs +1 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +2 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +36 -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;
|
|
@@ -8,8 +8,14 @@ export const DINGTALK_REGISTRATION_BASE_URL = 'https://oapi.dingtalk.com/';
|
|
|
8
8
|
export const DINGTALK_API_BASE_URL = 'https://api.dingtalk.com/';
|
|
9
9
|
export const DINGTALK_REGISTRATION_SOURCE = 'DING_DWS_CLAW';
|
|
10
10
|
export const DINGTALK_AI_CARD_TEMPLATE_ID = '02fcf2f4-5e02-4a85-b672-46d1f715543e.schema';
|
|
11
|
+
export const DINGTALK_THINKING_REACTION_NAME = '🤔思考中';
|
|
12
|
+
export const DINGTALK_DONE_REACTION_NAME = '✅已完成';
|
|
13
|
+
export const DINGTALK_ERROR_REACTION_NAME = '❌处理失败';
|
|
11
14
|
|
|
12
15
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
16
|
+
const REACTION_TIMEOUT_MS = 5_000;
|
|
17
|
+
const TEXT_REACTION_ID = '2659900';
|
|
18
|
+
const TEXT_REACTION_BACKGROUND_ID = 'im_bg_1';
|
|
13
19
|
const REGISTRATION_STATUSES = new Set(['WAITING', 'SUCCESS', 'FAIL', 'EXPIRED']);
|
|
14
20
|
|
|
15
21
|
export class DingtalkApiError extends Error {
|
|
@@ -406,18 +412,20 @@ export function createDingtalkApi({
|
|
|
406
412
|
if (!source) throw new TypeError('registrationSource is required');
|
|
407
413
|
const tokenCache = new Map();
|
|
408
414
|
const tokenRequests = new Map();
|
|
415
|
+
const reactionTokenRequests = new Map();
|
|
409
416
|
let cardSlotTail = Promise.resolve();
|
|
410
417
|
let nextCardRequestAt = 0;
|
|
411
418
|
|
|
412
419
|
const endpoint = (base, pathname) => new URL(pathname.replace(/^\//, ''), base);
|
|
413
420
|
|
|
414
|
-
async function accessToken({ clientId, clientSecret, signal }) {
|
|
421
|
+
async function accessToken({ clientId, clientSecret, signal, requestKind = 'normal' }) {
|
|
415
422
|
const appKey = nonEmptyString(clientId);
|
|
416
423
|
const appSecret = nonEmptyString(clientSecret);
|
|
417
424
|
if (!appKey || !appSecret) throw new TypeError('clientId and clientSecret are required');
|
|
418
425
|
const cached = tokenCache.get(appKey);
|
|
419
426
|
if (cached && cached.expiresAt > now()) return cached.token;
|
|
420
|
-
|
|
427
|
+
const requests = requestKind === 'reaction' ? reactionTokenRequests : tokenRequests;
|
|
428
|
+
if (requests.has(appKey)) return requests.get(appKey);
|
|
421
429
|
|
|
422
430
|
const request = (async () => {
|
|
423
431
|
const value = await requestJson(fetchImpl, endpoint(apiBase, 'v1.0/oauth2/accessToken'), {
|
|
@@ -431,9 +439,75 @@ export function createDingtalkApi({
|
|
|
431
439
|
const refreshAfterMs = Math.max(1_000, (expiresInSeconds - 60) * 1_000);
|
|
432
440
|
tokenCache.set(appKey, { token, expiresAt: now() + refreshAfterMs });
|
|
433
441
|
return token;
|
|
434
|
-
})()
|
|
435
|
-
|
|
436
|
-
|
|
442
|
+
})();
|
|
443
|
+
const shared = request.finally(() => requests.delete(appKey));
|
|
444
|
+
requests.set(appKey, shared);
|
|
445
|
+
return shared;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function changeReaction({
|
|
449
|
+
clientId,
|
|
450
|
+
clientSecret,
|
|
451
|
+
robotCode,
|
|
452
|
+
messageId,
|
|
453
|
+
conversationId,
|
|
454
|
+
reactionName = DINGTALK_THINKING_REACTION_NAME,
|
|
455
|
+
signal,
|
|
456
|
+
}, action) {
|
|
457
|
+
const appKey = nonEmptyString(clientId);
|
|
458
|
+
const appSecret = nonEmptyString(clientSecret);
|
|
459
|
+
const botCode = nonEmptyString(robotCode) ?? appKey;
|
|
460
|
+
const openMsgId = nonEmptyString(messageId);
|
|
461
|
+
const openConversationId = nonEmptyString(conversationId);
|
|
462
|
+
const emotionName = nonEmptyString(reactionName);
|
|
463
|
+
if (!appKey || !appSecret) throw new TypeError('clientId and clientSecret are required');
|
|
464
|
+
if (!botCode) throw new TypeError('robotCode is required');
|
|
465
|
+
if (!openMsgId || !openConversationId) {
|
|
466
|
+
throw new TypeError('messageId and conversationId are required');
|
|
467
|
+
}
|
|
468
|
+
if (!emotionName) throw new TypeError('reactionName is required');
|
|
469
|
+
// Reactions deduplicate with each other but never own the normal reply's
|
|
470
|
+
// in-flight token request. Both paths may still reuse a cached token.
|
|
471
|
+
const token = await accessToken({
|
|
472
|
+
clientId: appKey,
|
|
473
|
+
clientSecret: appSecret,
|
|
474
|
+
signal,
|
|
475
|
+
requestKind: 'reaction',
|
|
476
|
+
});
|
|
477
|
+
const response = await requestJson(
|
|
478
|
+
fetchImpl,
|
|
479
|
+
endpoint(apiBase, `v1.0/robot/emotion/${action}`),
|
|
480
|
+
{
|
|
481
|
+
body: {
|
|
482
|
+
robotCode: botCode,
|
|
483
|
+
openMsgId,
|
|
484
|
+
openConversationId,
|
|
485
|
+
emotionType: 2,
|
|
486
|
+
emotionName,
|
|
487
|
+
textEmotion: {
|
|
488
|
+
emotionId: TEXT_REACTION_ID,
|
|
489
|
+
emotionName,
|
|
490
|
+
text: emotionName,
|
|
491
|
+
backgroundId: TEXT_REACTION_BACKGROUND_ID,
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
headers: { 'x-acs-dingtalk-access-token': token },
|
|
495
|
+
signal,
|
|
496
|
+
timeoutMs: REACTION_TIMEOUT_MS,
|
|
497
|
+
action: action === 'reply' ? '消息状态添加' : '消息状态撤回',
|
|
498
|
+
},
|
|
499
|
+
);
|
|
500
|
+
const rejection = response?.success === false
|
|
501
|
+
? safeProviderCode(response?.code ?? response?.errcode) ?? 'rejected'
|
|
502
|
+
: rejectedProviderResponse(response);
|
|
503
|
+
if (rejection) {
|
|
504
|
+
throw new DingtalkApiError(
|
|
505
|
+
'reaction-rejected',
|
|
506
|
+
'钉钉服务拒绝了消息状态请求。',
|
|
507
|
+
{ providerCode: rejection },
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
437
511
|
}
|
|
438
512
|
|
|
439
513
|
async function messageFileDownloadUrl({
|
|
@@ -690,6 +764,28 @@ export function createDingtalkApi({
|
|
|
690
764
|
|
|
691
765
|
accessToken,
|
|
692
766
|
|
|
767
|
+
async addReaction(request) {
|
|
768
|
+
return changeReaction(request, 'reply');
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
async recallReaction(request) {
|
|
772
|
+
return changeReaction(request, 'recall');
|
|
773
|
+
},
|
|
774
|
+
|
|
775
|
+
async addThinkingReaction(request) {
|
|
776
|
+
return changeReaction({
|
|
777
|
+
...request,
|
|
778
|
+
reactionName: DINGTALK_THINKING_REACTION_NAME,
|
|
779
|
+
}, 'reply');
|
|
780
|
+
},
|
|
781
|
+
|
|
782
|
+
async recallThinkingReaction(request) {
|
|
783
|
+
return changeReaction({
|
|
784
|
+
...request,
|
|
785
|
+
reactionName: DINGTALK_THINKING_REACTION_NAME,
|
|
786
|
+
}, 'recall');
|
|
787
|
+
},
|
|
788
|
+
|
|
693
789
|
async downloadImage({
|
|
694
790
|
clientId,
|
|
695
791
|
clientSecret,
|
|
@@ -830,7 +926,7 @@ export function createDingtalkApi({
|
|
|
830
926
|
clientId: appKey,
|
|
831
927
|
clientSecret: appSecret,
|
|
832
928
|
cardInstanceId,
|
|
833
|
-
text: t('
|
|
929
|
+
text: t('卡片已结束,请查看后续消息。'),
|
|
834
930
|
signal: cleanupSignal,
|
|
835
931
|
}).catch(() => undefined);
|
|
836
932
|
}
|