@xmanrui/dsh-im 0.9.0 → 0.11.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 +7 -3
- package/README.md +7 -3
- package/lib/client.js +378 -27
- package/lib/index.js +134 -131
- package/package.json +1 -1
- package/plugin-src/client/channels/dingtalk/api.js +19 -0
- package/plugin-src/client/channels/dingtalk/index.js +67 -10
- package/plugin-src/client/channels/feishu/index.js +35 -5
- package/plugin-src/client/channels/qq/api.js +21 -0
- package/plugin-src/client/channels/qq/index.js +39 -1
- package/plugin-src/client/channels/shared/token-channel.js +29 -3
- package/plugin-src/client/channels/wecom/api.js +11 -0
- package/plugin-src/client/channels/wecom/index.js +71 -1
- package/plugin-src/client/channels/weixin/api.js +11 -0
- package/plugin-src/client/channels/weixin/index.js +40 -4
- package/plugin-src/client/channels/whatsapp/index.js +41 -2
- package/plugin-src/client/i18n.js +13 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +1 -1
- package/plugin-src/host/channels/dingtalk/rpc.mjs +29 -3
- package/plugin-src/host/channels/feishu/production.mjs +1 -1
- package/plugin-src/host/channels/feishu/rpc.mjs +34 -3
- package/plugin-src/host/channels/qq/production.mjs +1 -1
- package/plugin-src/host/channels/qq/rpc.mjs +35 -2
- package/plugin-src/host/channels/shared/production.mjs +1 -1
- package/plugin-src/host/channels/shared/rpc.mjs +25 -1
- package/plugin-src/host/channels/slack/production.mjs +1 -1
- package/plugin-src/host/channels/slack/rpc.mjs +28 -2
- package/plugin-src/host/channels/wecom/production.mjs +1 -1
- package/plugin-src/host/channels/wecom/rpc.mjs +31 -2
- package/plugin-src/host/channels/weixin/production.mjs +1 -1
- package/plugin-src/host/channels/weixin/rpc.mjs +29 -3
- package/plugin-src/host/channels/whatsapp/production.mjs +1 -1
- package/plugin-src/host/channels/whatsapp/rpc.mjs +31 -3
- package/src/channels/dingtalk/dingtalk-api.mjs +82 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +160 -20
- package/src/channels/dingtalk/dingtalk-controller.mjs +18 -0
- package/src/channels/dingtalk/dingtalk-runtime.mjs +21 -0
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/discord/discord-runtime.mjs +54 -1
- package/src/channels/feishu/bridge.mjs +45 -18
- package/src/channels/feishu/feishu-runtime.mjs +45 -0
- package/src/channels/feishu/message-utils.mjs +227 -6
- package/src/channels/feishu/multi-bot-controller.mjs +21 -0
- package/src/channels/feishu/plugin-controller.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +107 -20
- package/src/channels/qq/qq-controller.mjs +18 -0
- package/src/channels/qq/qq-runtime.mjs +26 -0
- package/src/channels/shared/bot-workspace-store.mjs +2 -0
- package/src/channels/shared/connection-test.mjs +54 -0
- package/src/channels/shared/harness-client.mjs +14 -8
- package/src/channels/shared/image-prompt.mjs +268 -0
- package/src/channels/shared/text-harness-bridge.mjs +62 -16
- package/src/channels/shared/token-bot-controller.mjs +21 -0
- package/src/channels/shared/workspace-session.mjs +2 -1
- package/src/channels/slack/manifest.mjs +1 -0
- package/src/channels/slack/slack-api.mjs +95 -0
- package/src/channels/slack/slack-controller.mjs +19 -0
- package/src/channels/slack/slack-runtime.mjs +34 -3
- package/src/channels/telegram/telegram-api.mjs +37 -0
- package/src/channels/telegram/telegram-runtime.mjs +81 -9
- package/src/channels/wecom/wecom-bridge.mjs +160 -23
- package/src/channels/wecom/wecom-controller.mjs +18 -0
- package/src/channels/wecom/wecom-runtime.mjs +18 -0
- package/src/channels/weixin/weixin-api.mjs +98 -2
- package/src/channels/weixin/weixin-bridge.mjs +52 -19
- package/src/channels/weixin/weixin-controller.mjs +18 -0
- package/src/channels/weixin/weixin-runtime.mjs +25 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +20 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +160 -1
|
@@ -151,6 +151,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
|
|
|
151
151
|
export function AccountCard({
|
|
152
152
|
account,
|
|
153
153
|
busy,
|
|
154
|
+
feedback,
|
|
154
155
|
removing,
|
|
155
156
|
onReconnect,
|
|
156
157
|
onWorkspaceSave,
|
|
@@ -180,6 +181,11 @@ export function AccountCard({
|
|
|
180
181
|
}),
|
|
181
182
|
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
|
182
183
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
184
|
+
feedback ? h('div', {
|
|
185
|
+
className: 'ddt-summary dim-cardSummary',
|
|
186
|
+
role: 'status',
|
|
187
|
+
'aria-live': 'polite',
|
|
188
|
+
}, feedback) : null,
|
|
183
189
|
h('div', { className: 'ddt-actions dim-cardActions' },
|
|
184
190
|
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
|
185
191
|
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))),
|
|
@@ -193,13 +199,31 @@ export function WecomSettingsTab({ rpcCall }) {
|
|
|
193
199
|
const [provision, setProvision] = React.useState(null);
|
|
194
200
|
const [busy, setBusy] = React.useState(false);
|
|
195
201
|
const [busyByBot, setBusyByBot] = React.useState({});
|
|
202
|
+
const [feedbackByBot, setFeedbackByBot] = React.useState({});
|
|
196
203
|
const [removeTarget, setRemoveTarget] = React.useState(null);
|
|
197
204
|
const [credentialOpen, setCredentialOpen] = React.useState(false);
|
|
198
205
|
const [credentialError, setCredentialError] = React.useState(null);
|
|
206
|
+
const [notice, setNotice] = React.useState('');
|
|
199
207
|
const [now, setNow] = React.useState(Date.now());
|
|
200
208
|
const mounted = React.useRef(true);
|
|
201
209
|
const workspaceFence = useWorkspaceSnapshotFence();
|
|
202
210
|
const addButtonRef = React.useRef(null);
|
|
211
|
+
const noticeFrameRef = React.useRef(null);
|
|
212
|
+
|
|
213
|
+
const announce = React.useCallback((message) => {
|
|
214
|
+
if (!mounted.current) return;
|
|
215
|
+
if (noticeFrameRef.current !== null) {
|
|
216
|
+
window.cancelAnimationFrame(noticeFrameRef.current);
|
|
217
|
+
noticeFrameRef.current = null;
|
|
218
|
+
}
|
|
219
|
+
setNotice('');
|
|
220
|
+
if (message) {
|
|
221
|
+
noticeFrameRef.current = window.requestAnimationFrame(() => {
|
|
222
|
+
noticeFrameRef.current = null;
|
|
223
|
+
if (mounted.current) setNotice(message);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}, []);
|
|
203
227
|
|
|
204
228
|
React.useEffect(() => {
|
|
205
229
|
const disposeDingtalk = installDingtalkStyles();
|
|
@@ -207,6 +231,10 @@ export function WecomSettingsTab({ rpcCall }) {
|
|
|
207
231
|
mounted.current = true;
|
|
208
232
|
return () => {
|
|
209
233
|
mounted.current = false;
|
|
234
|
+
if (noticeFrameRef.current !== null) {
|
|
235
|
+
window.cancelAnimationFrame(noticeFrameRef.current);
|
|
236
|
+
noticeFrameRef.current = null;
|
|
237
|
+
}
|
|
210
238
|
disposeWecom();
|
|
211
239
|
disposeDingtalk();
|
|
212
240
|
};
|
|
@@ -361,6 +389,46 @@ export function WecomSettingsTab({ rpcCall }) {
|
|
|
361
389
|
}
|
|
362
390
|
}, [invoke, loadStatus, workspaceFence]);
|
|
363
391
|
|
|
392
|
+
const reconnect = React.useCallback(async (account) => {
|
|
393
|
+
setFeedbackByBot((current) => {
|
|
394
|
+
const next = { ...current };
|
|
395
|
+
delete next[account.botId];
|
|
396
|
+
return next;
|
|
397
|
+
});
|
|
398
|
+
try {
|
|
399
|
+
const snapshot = await botAction(
|
|
400
|
+
account,
|
|
401
|
+
'reconnect',
|
|
402
|
+
WECOM_ENDPOINTS.reconnectBot,
|
|
403
|
+
{ botId: account.botId, sendTest: true },
|
|
404
|
+
);
|
|
405
|
+
if (!snapshot) return;
|
|
406
|
+
const refreshed = snapshot.bots.find((bot) => bot.botId === account.botId);
|
|
407
|
+
let feedback;
|
|
408
|
+
if (!refreshed?.connected) {
|
|
409
|
+
feedback = '企业微信仍未连接,插件会继续自动重试。';
|
|
410
|
+
} else if (snapshot.testMessage?.sent) {
|
|
411
|
+
feedback = '企业微信连接检查完成,测试消息已发送。';
|
|
412
|
+
} else if (snapshot.testMessage?.code === 'test-target-unavailable') {
|
|
413
|
+
feedback = '连接检查完成。机器人尚未收到可用于测试的私聊消息。';
|
|
414
|
+
} else if (snapshot.testMessage) {
|
|
415
|
+
feedback = '企业微信连接检查完成,但测试消息发送失败。';
|
|
416
|
+
} else {
|
|
417
|
+
feedback = '企业微信连接检查完成。';
|
|
418
|
+
}
|
|
419
|
+
if (mounted.current) {
|
|
420
|
+
setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
|
|
421
|
+
}
|
|
422
|
+
announce(feedback);
|
|
423
|
+
} catch {
|
|
424
|
+
const feedback = '连接检查失败,请稍后重试。';
|
|
425
|
+
if (mounted.current) {
|
|
426
|
+
setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
|
|
427
|
+
}
|
|
428
|
+
announce(feedback);
|
|
429
|
+
}
|
|
430
|
+
}, [announce, botAction]);
|
|
431
|
+
|
|
364
432
|
let provisionView = null;
|
|
365
433
|
if (provision?.status === 'starting') provisionView = h('div', { className: 'ddt-card ddt-loading dim-surfaceCard' }, h('div', { className: 'ddt-spinner' }), '正在申请企业微信二维码…');
|
|
366
434
|
else if (['pending', 'refreshing'].includes(provision?.status)) provisionView = h(QrPanel, {
|
|
@@ -378,8 +446,9 @@ export function WecomSettingsTab({ rpcCall }) {
|
|
|
378
446
|
h('li', { key: account.botId }, h(AccountCard, {
|
|
379
447
|
account,
|
|
380
448
|
busy: busyByBot[account.botId],
|
|
449
|
+
feedback: feedbackByBot[account.botId],
|
|
381
450
|
removing: removeTarget === account.botId,
|
|
382
|
-
onReconnect: () => void
|
|
451
|
+
onReconnect: () => void reconnect(account),
|
|
383
452
|
onWorkspaceSave: (workspace) => botAction(
|
|
384
453
|
account,
|
|
385
454
|
'workspace',
|
|
@@ -419,6 +488,7 @@ export function WecomSettingsTab({ rpcCall }) {
|
|
|
419
488
|
credentialOpen,
|
|
420
489
|
addButtonRef,
|
|
421
490
|
}),
|
|
491
|
+
h('div', { className: 'ddt-visuallyHidden', role: 'status', 'aria-live': 'polite' }, notice),
|
|
422
492
|
model.phase === 'loading' ? h(LoadingView)
|
|
423
493
|
: model.phase === 'error'
|
|
424
494
|
? h('div', { className: 'ddt-card dim-surfaceCard' }, h('div', { className: 'ddt-inlineError dim-inlineError' }, h('h3', null, '无法读取企业微信机器人状态'), h('p', null, model.error?.message), h(Button, { onClick: () => void loadStatus() }, '重新读取')))
|
|
@@ -35,6 +35,16 @@ function timestamp(value) {
|
|
|
35
35
|
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function normalizeTestMessage(value) {
|
|
39
|
+
if (!isRecord(value)) return null;
|
|
40
|
+
if (value.sent === true) return { sent: true };
|
|
41
|
+
if (value.sent !== false) return null;
|
|
42
|
+
const code = value.code === 'test-target-unavailable'
|
|
43
|
+
? 'test-target-unavailable'
|
|
44
|
+
: 'test-message-failed';
|
|
45
|
+
return { sent: false, code };
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
export function unwrapRpcResult(result) {
|
|
39
49
|
if (!isRecord(result) || typeof result.ok !== 'boolean') {
|
|
40
50
|
throw new Error('微信服务返回了无法识别的响应');
|
|
@@ -142,6 +152,7 @@ export function normalizeSnapshot(value) {
|
|
|
142
152
|
connected: bots.filter((bot) => bot.connected).length,
|
|
143
153
|
},
|
|
144
154
|
provisioning: value.provisioning ? normalizeProvisioning(value.provisioning) : null,
|
|
155
|
+
testMessage: normalizeTestMessage(value.testMessage),
|
|
145
156
|
};
|
|
146
157
|
}
|
|
147
158
|
|
|
@@ -198,6 +198,7 @@ function checkedTime(timestamp) {
|
|
|
198
198
|
export function AccountCard({
|
|
199
199
|
account,
|
|
200
200
|
busy,
|
|
201
|
+
feedback,
|
|
201
202
|
removing,
|
|
202
203
|
onReconnect,
|
|
203
204
|
onWorkspaceSave,
|
|
@@ -229,6 +230,11 @@ export function AccountCard({
|
|
|
229
230
|
}),
|
|
230
231
|
h('div', { className: 'dxw-accountFooter dim-cardFooter' },
|
|
231
232
|
summary ? h('div', { className: 'dxw-summary dim-cardSummary' }, summary) : null,
|
|
233
|
+
feedback ? h('div', {
|
|
234
|
+
className: 'dxw-summary dim-cardSummary',
|
|
235
|
+
role: 'status',
|
|
236
|
+
'aria-live': 'polite',
|
|
237
|
+
}, feedback) : null,
|
|
232
238
|
h('div', { className: 'dxw-actions dim-cardActions' },
|
|
233
239
|
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) },
|
|
234
240
|
busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
|
@@ -250,6 +256,7 @@ function AccountList(props) {
|
|
|
250
256
|
h(AccountCard, {
|
|
251
257
|
account,
|
|
252
258
|
busy: props.busyByBot[account.botId],
|
|
259
|
+
feedback: props.feedbackByBot[account.botId],
|
|
253
260
|
removing: props.removeTarget === account.botId,
|
|
254
261
|
onReconnect: () => props.onReconnect(account),
|
|
255
262
|
onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
|
|
@@ -282,6 +289,7 @@ export function WeixinSettingsTab({ rpcCall }) {
|
|
|
282
289
|
const [provision, setProvision] = React.useState(null);
|
|
283
290
|
const [busy, setBusy] = React.useState(false);
|
|
284
291
|
const [busyByBot, setBusyByBot] = React.useState({});
|
|
292
|
+
const [feedbackByBot, setFeedbackByBot] = React.useState({});
|
|
285
293
|
const [removeTarget, setRemoveTarget] = React.useState(null);
|
|
286
294
|
const [notice, setNotice] = React.useState('');
|
|
287
295
|
const [now, setNow] = React.useState(() => Date.now());
|
|
@@ -496,15 +504,42 @@ export function WeixinSettingsTab({ rpcCall }) {
|
|
|
496
504
|
const reconnect = React.useCallback(async (account) => {
|
|
497
505
|
const snapshotVersion = workspaceFence.beginMutation();
|
|
498
506
|
setBotBusy(account.botId, 'reconnect');
|
|
507
|
+
setFeedbackByBot((current) => {
|
|
508
|
+
const next = { ...current };
|
|
509
|
+
delete next[account.botId];
|
|
510
|
+
return next;
|
|
511
|
+
});
|
|
499
512
|
try {
|
|
500
|
-
const snapshot = normalizeSnapshot(await invoke(
|
|
513
|
+
const snapshot = normalizeSnapshot(await invoke(
|
|
514
|
+
WEIXIN_ENDPOINTS.reconnectBot,
|
|
515
|
+
{ botId: account.botId, sendTest: true },
|
|
516
|
+
));
|
|
501
517
|
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
502
518
|
setModel((current) => ({ ...current, bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision }));
|
|
503
519
|
}
|
|
504
520
|
const refreshed = snapshot.bots.find((bot) => bot.botId === account.botId);
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
521
|
+
let feedback;
|
|
522
|
+
if (!refreshed?.connected) {
|
|
523
|
+
feedback = '微信仍未连接,插件会继续自动重试。';
|
|
524
|
+
} else if (snapshot.testMessage?.sent) {
|
|
525
|
+
feedback = '微信连接检查完成,测试消息已发送。';
|
|
526
|
+
} else if (snapshot.testMessage?.code === 'test-target-unavailable') {
|
|
527
|
+
feedback = '连接检查完成。机器人尚未收到可用于测试的私聊消息。';
|
|
528
|
+
} else if (snapshot.testMessage) {
|
|
529
|
+
feedback = '微信连接检查完成,但测试消息发送失败。';
|
|
530
|
+
} else {
|
|
531
|
+
feedback = '微信连接检查完成。';
|
|
532
|
+
}
|
|
533
|
+
if (mountedRef.current) {
|
|
534
|
+
setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
|
|
535
|
+
}
|
|
536
|
+
announce(feedback);
|
|
537
|
+
} catch {
|
|
538
|
+
const feedback = '连接检查失败,请稍后重试。';
|
|
539
|
+
if (mountedRef.current) {
|
|
540
|
+
setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
|
|
541
|
+
}
|
|
542
|
+
announce(feedback);
|
|
508
543
|
} finally {
|
|
509
544
|
const shouldRefresh = workspaceFence.endMutation();
|
|
510
545
|
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
@@ -611,6 +646,7 @@ export function WeixinSettingsTab({ rpcCall }) {
|
|
|
611
646
|
? h(AccountList, {
|
|
612
647
|
bots: model.bots,
|
|
613
648
|
busyByBot,
|
|
649
|
+
feedbackByBot,
|
|
614
650
|
removeTarget,
|
|
615
651
|
onReconnect: (account) => void reconnect(account),
|
|
616
652
|
onWorkspaceSave: saveWorkspace,
|
|
@@ -43,6 +43,16 @@ function checkedTime(value) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function connectionTestNotice(value) {
|
|
47
|
+
if (value?.testMessage?.sent === true) {
|
|
48
|
+
return '测试消息已发送,请到 WhatsApp 自聊会话中确认。';
|
|
49
|
+
}
|
|
50
|
+
if (value?.testMessage?.code === 'test-target-unavailable') {
|
|
51
|
+
return '连接检查完成,但当前没有可用的 WhatsApp 自聊目标。';
|
|
52
|
+
}
|
|
53
|
+
return value?.testMessage ? '连接检查完成,但测试消息发送失败。' : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
function Heading({ totals, busy, onAdd, addButtonRef }) {
|
|
47
57
|
return h('div', { className: 'ddt-heading' },
|
|
48
58
|
h('div', { className: 'ddt-tools' },
|
|
@@ -159,6 +169,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
|
|
|
159
169
|
export function WhatsappAccountCard({
|
|
160
170
|
account,
|
|
161
171
|
busy,
|
|
172
|
+
testNotice,
|
|
162
173
|
removing,
|
|
163
174
|
onReconnect,
|
|
164
175
|
onWorkspaceSave,
|
|
@@ -196,6 +207,10 @@ export function WhatsappAccountCard({
|
|
|
196
207
|
}),
|
|
197
208
|
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
|
198
209
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
210
|
+
testNotice ? h('div', {
|
|
211
|
+
className: 'ddt-summary dim-cardSummary',
|
|
212
|
+
role: 'status',
|
|
213
|
+
}, testNotice) : null,
|
|
199
214
|
h('div', { className: 'ddt-actions dim-cardActions' },
|
|
200
215
|
h(Button, {
|
|
201
216
|
className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy),
|
|
@@ -218,6 +233,7 @@ export function WhatsappSettingsTab({ rpcCall }) {
|
|
|
218
233
|
const [provision, setProvision] = React.useState(null);
|
|
219
234
|
const [busy, setBusy] = React.useState(false);
|
|
220
235
|
const [busyByBot, setBusyByBot] = React.useState({});
|
|
236
|
+
const [testNoticeByBot, setTestNoticeByBot] = React.useState({});
|
|
221
237
|
const [removeTarget, setRemoveTarget] = React.useState(null);
|
|
222
238
|
const [now, setNow] = React.useState(Date.now());
|
|
223
239
|
const mounted = React.useRef(true);
|
|
@@ -365,10 +381,32 @@ export function WhatsappSettingsTab({ rpcCall }) {
|
|
|
365
381
|
const botAction = React.useCallback(async (account, operation, endpoint, payload) => {
|
|
366
382
|
const snapshotVersion = workspaceFence.beginMutation();
|
|
367
383
|
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
|
|
384
|
+
if (operation === 'reconnect') {
|
|
385
|
+
setTestNoticeByBot((current) => {
|
|
386
|
+
const next = { ...current };
|
|
387
|
+
delete next[account.botId];
|
|
388
|
+
return next;
|
|
389
|
+
});
|
|
390
|
+
}
|
|
368
391
|
try {
|
|
369
|
-
const
|
|
392
|
+
const value = await invoke(endpoint, payload);
|
|
393
|
+
const snapshot = normalizeSnapshot(value);
|
|
370
394
|
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
371
395
|
setModel({ phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null });
|
|
396
|
+
if (operation === 'reconnect') {
|
|
397
|
+
setTestNoticeByBot((current) => ({
|
|
398
|
+
...current,
|
|
399
|
+
[account.botId]: connectionTestNotice(value),
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (operation !== 'reconnect') throw error;
|
|
405
|
+
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
406
|
+
setTestNoticeByBot((current) => ({
|
|
407
|
+
...current,
|
|
408
|
+
[account.botId]: '连接检查失败,请稍后重试。',
|
|
409
|
+
}));
|
|
372
410
|
}
|
|
373
411
|
} finally {
|
|
374
412
|
const shouldRefresh = workspaceFence.endMutation();
|
|
@@ -389,12 +427,13 @@ export function WhatsappSettingsTab({ rpcCall }) {
|
|
|
389
427
|
h('li', { key: account.botId }, h(WhatsappAccountCard, {
|
|
390
428
|
account,
|
|
391
429
|
busy: busyByBot[account.botId],
|
|
430
|
+
testNotice: testNoticeByBot[account.botId],
|
|
392
431
|
removing: removeTarget === account.botId,
|
|
393
432
|
onReconnect: () => void botAction(
|
|
394
433
|
account,
|
|
395
434
|
'reconnect',
|
|
396
435
|
WHATSAPP_ENDPOINTS.reconnectBot,
|
|
397
|
-
{ botId: account.botId },
|
|
436
|
+
{ botId: account.botId, sendTest: true },
|
|
398
437
|
),
|
|
399
438
|
onWorkspaceSave: (workspace) => botAction(
|
|
400
439
|
account,
|
|
@@ -75,6 +75,19 @@ const EN = Object.freeze({
|
|
|
75
75
|
'刚刚': 'Just now',
|
|
76
76
|
'检查连接': 'Check connection',
|
|
77
77
|
'检查中…': 'Checking…',
|
|
78
|
+
'连接检查失败,请稍后重试。': 'Connection check failed. Try again later.',
|
|
79
|
+
'测试消息已发送,请到对应机器人会话中确认。': 'Test message sent. Check the matching bot conversation.',
|
|
80
|
+
'连接检查完成。机器人尚未收到可用于测试的私聊消息。': 'Connection check completed. The bot has not received a direct message it can use for testing.',
|
|
81
|
+
'连接检查完成,但测试消息发送失败。': 'Connection check completed, but the test message could not be sent.',
|
|
82
|
+
'测试消息已发送,请到飞书会话中确认。': 'Test message sent. Check the Feishu conversation.',
|
|
83
|
+
'测试消息已发送,请到 WhatsApp 自聊会话中确认。': 'Test message sent. Check the WhatsApp self-chat.',
|
|
84
|
+
'连接检查完成,但当前没有可用的 WhatsApp 自聊目标。': 'Connection check completed, but no WhatsApp self-chat target is available.',
|
|
85
|
+
'钉钉连接检查完成,测试消息已发送。': 'DingTalk connection check completed and the test message was sent.',
|
|
86
|
+
'钉钉连接检查完成,但测试消息发送失败。': 'DingTalk connection check completed, but the test message could not be sent.',
|
|
87
|
+
'微信连接检查完成,测试消息已发送。': 'WeChat connection check completed and the test message was sent.',
|
|
88
|
+
'微信连接检查完成,但测试消息发送失败。': 'WeChat connection check completed, but the test message could not be sent.',
|
|
89
|
+
'企业微信连接检查完成,测试消息已发送。': 'WeCom connection check completed and the test message was sent.',
|
|
90
|
+
'企业微信连接检查完成,但测试消息发送失败。': 'WeCom connection check completed, but the test message could not be sent.',
|
|
78
91
|
'重试连接': 'Reconnect',
|
|
79
92
|
'重试中…': 'Retrying…',
|
|
80
93
|
'移除接入': 'Remove connection',
|
|
@@ -84,7 +84,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
84
84
|
const harness = new Harness({
|
|
85
85
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
86
86
|
workspace: defaultWorkspace,
|
|
87
|
-
agentPreset: config.agentPreset
|
|
87
|
+
...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
|
|
88
88
|
autostart: false,
|
|
89
89
|
dshBin: config.dshBin ?? 'dsh',
|
|
90
90
|
...(commandExecutor ? { commandExecutor } : {}),
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
2
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
3
3
|
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
|
4
|
+
import {
|
|
5
|
+
connectionTestTargetUnavailable,
|
|
6
|
+
publicConnectionTestResult,
|
|
7
|
+
} from '../../../../src/channels/shared/connection-test.mjs';
|
|
4
8
|
|
|
5
9
|
export const DINGTALK_RPC_CHANNEL = '/dingtalk';
|
|
6
10
|
export const DINGTALK_ENDPOINTS = Object.freeze({
|
|
@@ -69,9 +73,11 @@ function payloadFailure(endpoint, payload) {
|
|
|
69
73
|
: 'bot.bind-credentials requires Client ID and Client Secret.';
|
|
70
74
|
}
|
|
71
75
|
if (endpoint === DINGTALK_ENDPOINTS.reconnectBot) {
|
|
72
|
-
return exactKeys(payload, ['botId'])
|
|
76
|
+
return exactKeys(payload, ['botId', 'sendTest'])
|
|
77
|
+
&& validId(payload.botId)
|
|
78
|
+
&& (payload.sendTest === undefined || payload.sendTest === true)
|
|
73
79
|
? null
|
|
74
|
-
: 'bot.reconnect requires a botId.';
|
|
80
|
+
: 'bot.reconnect requires a botId and optional sendTest=true.';
|
|
75
81
|
}
|
|
76
82
|
if (endpoint === DINGTALK_ENDPOINTS.deleteBot) {
|
|
77
83
|
return exactKeys(payload, ['botId', 'confirm']) && validId(payload.botId) && payload.confirm === true
|
|
@@ -210,7 +216,27 @@ export function createDingtalkRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|
|
210
216
|
} else if (endpoint === DINGTALK_ENDPOINTS.bindCredentials) {
|
|
211
217
|
value = await publicStatus(await controller.bindCredentials(payload), cachedEncode);
|
|
212
218
|
} else if (endpoint === DINGTALK_ENDPOINTS.reconnectBot) {
|
|
213
|
-
|
|
219
|
+
const snapshot = await controller.reconnectBot(payload.botId);
|
|
220
|
+
if (signal?.aborted) return cancelled();
|
|
221
|
+
let testMessage;
|
|
222
|
+
if (payload.sendTest === true) {
|
|
223
|
+
const connected = snapshot?.bots?.some(
|
|
224
|
+
(bot) => bot?.botId === payload.botId && bot?.connected === true,
|
|
225
|
+
);
|
|
226
|
+
if (!connected || typeof controller.sendConnectionTest !== 'function') {
|
|
227
|
+
testMessage = publicConnectionTestResult(
|
|
228
|
+
connectionTestTargetUnavailable('钉钉机器人'),
|
|
229
|
+
);
|
|
230
|
+
} else {
|
|
231
|
+
try {
|
|
232
|
+
await controller.sendConnectionTest(payload.botId);
|
|
233
|
+
testMessage = publicConnectionTestResult();
|
|
234
|
+
} catch (error) {
|
|
235
|
+
testMessage = publicConnectionTestResult(error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
value = await publicStatus({ ...snapshot, ...(testMessage ? { testMessage } : {}) }, cachedEncode);
|
|
214
240
|
} else if (endpoint === DINGTALK_ENDPOINTS.deleteBot) {
|
|
215
241
|
value = await publicStatus(await controller.deleteBot(payload.botId), cachedEncode);
|
|
216
242
|
} else if (endpoint === DINGTALK_ENDPOINTS.setWorkspace) {
|
|
@@ -110,7 +110,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
110
110
|
const harness = new Harness({
|
|
111
111
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
112
112
|
workspace: defaultWorkspace,
|
|
113
|
-
agentPreset: config.agentPreset
|
|
113
|
+
...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
|
|
114
114
|
// This plugin is already hosted by a running DSH process. Starting a
|
|
115
115
|
// second DSH would create a competing server and lifecycle.
|
|
116
116
|
autostart: false,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
|
+
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
|
2
3
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
3
4
|
import { publicWorkspaceError, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
|
4
5
|
import {
|
|
@@ -258,8 +259,14 @@ function validPayload(endpoint, payload) {
|
|
|
258
259
|
? null
|
|
259
260
|
: 'Disconnect requires removeCredentials=true.';
|
|
260
261
|
}
|
|
261
|
-
if (endpoint === FEISHU_MULTI_ENDPOINTS.reconnectBot
|
|
262
|
-
|
|
262
|
+
if (endpoint === FEISHU_MULTI_ENDPOINTS.reconnectBot) {
|
|
263
|
+
return hasOnlyKeys(payload, new Set(['botId', 'sendTest']))
|
|
264
|
+
&& safeOpaqueId(payload.botId)
|
|
265
|
+
&& (payload.sendTest === undefined || typeof payload.sendTest === 'boolean')
|
|
266
|
+
? null
|
|
267
|
+
: 'A valid botId and optional sendTest flag are required.';
|
|
268
|
+
}
|
|
269
|
+
if (endpoint === FEISHU_MULTI_ENDPOINTS.disconnectBot) {
|
|
263
270
|
return hasOnlyKeys(payload, new Set(['botId'])) && safeOpaqueId(payload.botId)
|
|
264
271
|
? null
|
|
265
272
|
: 'A single valid botId is required.';
|
|
@@ -431,7 +438,31 @@ export function createFeishuRpcHandler(controller, { encodeQr = qrCodeDataUrl }
|
|
|
431
438
|
value = await toPublicFeishuStatus(await controller.disconnect(), { encodeQr: cachedEncodeQr });
|
|
432
439
|
} else if (endpoint === FEISHU_MULTI_ENDPOINTS.reconnectBot) {
|
|
433
440
|
if (typeof controller.reconnectBot !== 'function') throw new Error('Multi-bot reconnect is unavailable');
|
|
434
|
-
|
|
441
|
+
const checked = await controller.reconnectBot(payload.botId);
|
|
442
|
+
if (signal?.aborted) return cancelled();
|
|
443
|
+
value = await toPublicFeishuStatus(checked, { encodeQr: cachedEncodeQr });
|
|
444
|
+
if (payload.sendTest === true) {
|
|
445
|
+
let testError = null;
|
|
446
|
+
const connected = checked?.bots?.some(
|
|
447
|
+
(bot) => bot?.botId === payload.botId && bot.connected === true,
|
|
448
|
+
) === true;
|
|
449
|
+
if (!connected) {
|
|
450
|
+
testError = new Error('Feishu bot is not connected');
|
|
451
|
+
testError.code = 'test-target-unavailable';
|
|
452
|
+
} else {
|
|
453
|
+
try {
|
|
454
|
+
if (typeof controller.sendConnectionTest !== 'function') {
|
|
455
|
+
const unavailable = new Error('Connection test is unavailable');
|
|
456
|
+
unavailable.code = 'test-target-unavailable';
|
|
457
|
+
throw unavailable;
|
|
458
|
+
}
|
|
459
|
+
await controller.sendConnectionTest(payload.botId);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
testError = error;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
value = { ...value, testMessage: publicConnectionTestResult(testError) };
|
|
465
|
+
}
|
|
435
466
|
} else if (endpoint === FEISHU_MULTI_ENDPOINTS.disconnectBot) {
|
|
436
467
|
if (typeof controller.disconnectBot !== 'function') throw new Error('Multi-bot disconnect is unavailable');
|
|
437
468
|
value = await toPublicFeishuStatus(await controller.disconnectBot(payload.botId), { encodeQr: cachedEncodeQr });
|
|
@@ -75,7 +75,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
75
75
|
const harness = new Harness({
|
|
76
76
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
77
77
|
workspace: defaultWorkspace,
|
|
78
|
-
agentPreset: config.agentPreset
|
|
78
|
+
...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
|
|
79
79
|
autostart: false,
|
|
80
80
|
dshBin: config.dshBin ?? 'dsh',
|
|
81
81
|
...(commandExecutor ? { commandExecutor } : {}),
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
|
+
import {
|
|
3
|
+
connectionTestTargetUnavailable,
|
|
4
|
+
publicConnectionTestResult,
|
|
5
|
+
} from '../../../../src/channels/shared/connection-test.mjs';
|
|
2
6
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
3
7
|
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
|
4
8
|
|
|
@@ -53,7 +57,10 @@ function payloadFailure(endpoint, payload) {
|
|
|
53
57
|
? null : 'bot.bind-credentials requires AppID and AppSecret.';
|
|
54
58
|
}
|
|
55
59
|
if (endpoint === QQ_ENDPOINTS.reconnectBot) {
|
|
56
|
-
return exactKeys(payload, ['botId'
|
|
60
|
+
return exactKeys(payload, ['botId', 'sendTest'])
|
|
61
|
+
&& validId(payload.botId)
|
|
62
|
+
&& (payload.sendTest === undefined || payload.sendTest === true)
|
|
63
|
+
? null : 'bot.reconnect requires a botId and accepts only sendTest=true.';
|
|
57
64
|
}
|
|
58
65
|
if (endpoint === QQ_ENDPOINTS.deleteBot) {
|
|
59
66
|
return exactKeys(payload, ['botId', 'confirm']) && validId(payload.botId) && payload.confirm === true
|
|
@@ -125,7 +132,33 @@ export function createQqRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
|
125
132
|
} else if (endpoint === QQ_ENDPOINTS.bindCredentials) {
|
|
126
133
|
value = await publicStatus(await controller.bindCredentials(payload), cachedEncode);
|
|
127
134
|
} else if (endpoint === QQ_ENDPOINTS.reconnectBot) {
|
|
128
|
-
|
|
135
|
+
const snapshot = await controller.reconnectBot(payload.botId);
|
|
136
|
+
if (signal?.aborted) {
|
|
137
|
+
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } };
|
|
138
|
+
}
|
|
139
|
+
let testMessage;
|
|
140
|
+
if (payload.sendTest === true) {
|
|
141
|
+
const connected = snapshot?.bots?.some(
|
|
142
|
+
(bot) => bot?.botId === payload.botId && bot?.connected === true,
|
|
143
|
+
) === true;
|
|
144
|
+
if (!connected || typeof controller.sendConnectionTest !== 'function') {
|
|
145
|
+
testMessage = publicConnectionTestResult(
|
|
146
|
+
connectionTestTargetUnavailable('QQ机器人'),
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
let testError = null;
|
|
150
|
+
try {
|
|
151
|
+
await controller.sendConnectionTest(payload.botId);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
testError = error;
|
|
154
|
+
}
|
|
155
|
+
testMessage = publicConnectionTestResult(testError);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
value = await publicStatus({
|
|
159
|
+
...snapshot,
|
|
160
|
+
...(testMessage ? { testMessage } : {}),
|
|
161
|
+
}, cachedEncode);
|
|
129
162
|
} else if (endpoint === QQ_ENDPOINTS.setWorkspace) {
|
|
130
163
|
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
|
131
164
|
value = await publicStatus(
|
|
@@ -69,7 +69,7 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|
|
69
69
|
const harness = new ResolvedHarness({
|
|
70
70
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
71
71
|
workspace: defaultWorkspace,
|
|
72
|
-
agentPreset: config.agentPreset
|
|
72
|
+
...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
|
|
73
73
|
autostart: false,
|
|
74
74
|
dshBin: config.dshBin ?? 'dsh',
|
|
75
75
|
...(commandExecutor ? { commandExecutor } : {}),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
2
|
+
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
|
2
3
|
import {
|
|
3
4
|
publicWorkspaceError,
|
|
4
5
|
SET_WORKSPACE_ENDPOINT,
|
|
@@ -44,7 +45,8 @@ function payloadFailure(endpoint, payload) {
|
|
|
44
45
|
? null : 'bot.bind-credentials requires a Bot Token.';
|
|
45
46
|
}
|
|
46
47
|
if (endpoint === TOKEN_BOT_ENDPOINTS.reconnectBot) {
|
|
47
|
-
return exactKeys(payload, ['botId']) && validId(payload.botId)
|
|
48
|
+
return exactKeys(payload, ['botId', 'sendTest']) && validId(payload.botId)
|
|
49
|
+
&& (payload.sendTest === undefined || typeof payload.sendTest === 'boolean')
|
|
48
50
|
? null : 'bot.reconnect requires a botId.';
|
|
49
51
|
}
|
|
50
52
|
if (endpoint === TOKEN_BOT_ENDPOINTS.deleteBot) {
|
|
@@ -105,6 +107,28 @@ export function createTokenBotRpcHandler(controller, { channel }) {
|
|
|
105
107
|
value = await controller.bindCredentials(payload);
|
|
106
108
|
} else if (endpoint === TOKEN_BOT_ENDPOINTS.reconnectBot) {
|
|
107
109
|
value = await controller.reconnectBot(payload.botId);
|
|
110
|
+
if (signal?.aborted) {
|
|
111
|
+
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } };
|
|
112
|
+
}
|
|
113
|
+
if (payload.sendTest === true) {
|
|
114
|
+
let testError = null;
|
|
115
|
+
try {
|
|
116
|
+
if (value?.bots?.find((bot) => bot?.botId === payload.botId)?.connected !== true) {
|
|
117
|
+
const unavailable = new Error('Bot is not connected');
|
|
118
|
+
unavailable.code = 'test-target-unavailable';
|
|
119
|
+
throw unavailable;
|
|
120
|
+
}
|
|
121
|
+
if (typeof controller.sendConnectionTest !== 'function') {
|
|
122
|
+
const unavailable = new Error('Connection test is unavailable');
|
|
123
|
+
unavailable.code = 'test-target-unavailable';
|
|
124
|
+
throw unavailable;
|
|
125
|
+
}
|
|
126
|
+
await controller.sendConnectionTest(payload.botId);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
testError = error;
|
|
129
|
+
}
|
|
130
|
+
value = { ...value, testMessage: publicConnectionTestResult(testError) };
|
|
131
|
+
}
|
|
108
132
|
} else if (endpoint === TOKEN_BOT_ENDPOINTS.setWorkspace) {
|
|
109
133
|
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
|
110
134
|
value = await controller.updateWorkspace(payload.botId, payload.workspace);
|
|
@@ -54,7 +54,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
54
54
|
const harness = new ResolvedHarness({
|
|
55
55
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
56
56
|
workspace: defaultWorkspace,
|
|
57
|
-
agentPreset: config.agentPreset
|
|
57
|
+
...(config.agentPreset == null ? {} : { agentPreset: config.agentPreset }),
|
|
58
58
|
autostart: false,
|
|
59
59
|
dshBin: config.dshBin ?? 'dsh',
|
|
60
60
|
...(commandExecutor ? { commandExecutor } : {}),
|