@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.
Files changed (69) hide show
  1. package/README.en.md +7 -3
  2. package/README.md +7 -3
  3. package/lib/client.js +378 -27
  4. package/lib/index.js +134 -131
  5. package/package.json +1 -1
  6. package/plugin-src/client/channels/dingtalk/api.js +19 -0
  7. package/plugin-src/client/channels/dingtalk/index.js +67 -10
  8. package/plugin-src/client/channels/feishu/index.js +35 -5
  9. package/plugin-src/client/channels/qq/api.js +21 -0
  10. package/plugin-src/client/channels/qq/index.js +39 -1
  11. package/plugin-src/client/channels/shared/token-channel.js +29 -3
  12. package/plugin-src/client/channels/wecom/api.js +11 -0
  13. package/plugin-src/client/channels/wecom/index.js +71 -1
  14. package/plugin-src/client/channels/weixin/api.js +11 -0
  15. package/plugin-src/client/channels/weixin/index.js +40 -4
  16. package/plugin-src/client/channels/whatsapp/index.js +41 -2
  17. package/plugin-src/client/i18n.js +13 -0
  18. package/plugin-src/host/channels/dingtalk/production.mjs +1 -1
  19. package/plugin-src/host/channels/dingtalk/rpc.mjs +29 -3
  20. package/plugin-src/host/channels/feishu/production.mjs +1 -1
  21. package/plugin-src/host/channels/feishu/rpc.mjs +34 -3
  22. package/plugin-src/host/channels/qq/production.mjs +1 -1
  23. package/plugin-src/host/channels/qq/rpc.mjs +35 -2
  24. package/plugin-src/host/channels/shared/production.mjs +1 -1
  25. package/plugin-src/host/channels/shared/rpc.mjs +25 -1
  26. package/plugin-src/host/channels/slack/production.mjs +1 -1
  27. package/plugin-src/host/channels/slack/rpc.mjs +28 -2
  28. package/plugin-src/host/channels/wecom/production.mjs +1 -1
  29. package/plugin-src/host/channels/wecom/rpc.mjs +31 -2
  30. package/plugin-src/host/channels/weixin/production.mjs +1 -1
  31. package/plugin-src/host/channels/weixin/rpc.mjs +29 -3
  32. package/plugin-src/host/channels/whatsapp/production.mjs +1 -1
  33. package/plugin-src/host/channels/whatsapp/rpc.mjs +31 -3
  34. package/src/channels/dingtalk/dingtalk-api.mjs +82 -1
  35. package/src/channels/dingtalk/dingtalk-bridge.mjs +160 -20
  36. package/src/channels/dingtalk/dingtalk-controller.mjs +18 -0
  37. package/src/channels/dingtalk/dingtalk-runtime.mjs +21 -0
  38. package/src/channels/discord/discord-api.mjs +1 -1
  39. package/src/channels/discord/discord-runtime.mjs +54 -1
  40. package/src/channels/feishu/bridge.mjs +45 -18
  41. package/src/channels/feishu/feishu-runtime.mjs +45 -0
  42. package/src/channels/feishu/message-utils.mjs +227 -6
  43. package/src/channels/feishu/multi-bot-controller.mjs +21 -0
  44. package/src/channels/feishu/plugin-controller.mjs +1 -0
  45. package/src/channels/qq/qq-bridge.mjs +107 -20
  46. package/src/channels/qq/qq-controller.mjs +18 -0
  47. package/src/channels/qq/qq-runtime.mjs +26 -0
  48. package/src/channels/shared/bot-workspace-store.mjs +2 -0
  49. package/src/channels/shared/connection-test.mjs +54 -0
  50. package/src/channels/shared/harness-client.mjs +14 -8
  51. package/src/channels/shared/image-prompt.mjs +268 -0
  52. package/src/channels/shared/text-harness-bridge.mjs +62 -16
  53. package/src/channels/shared/token-bot-controller.mjs +21 -0
  54. package/src/channels/shared/workspace-session.mjs +2 -1
  55. package/src/channels/slack/manifest.mjs +1 -0
  56. package/src/channels/slack/slack-api.mjs +95 -0
  57. package/src/channels/slack/slack-controller.mjs +19 -0
  58. package/src/channels/slack/slack-runtime.mjs +34 -3
  59. package/src/channels/telegram/telegram-api.mjs +37 -0
  60. package/src/channels/telegram/telegram-runtime.mjs +81 -9
  61. package/src/channels/wecom/wecom-bridge.mjs +160 -23
  62. package/src/channels/wecom/wecom-controller.mjs +18 -0
  63. package/src/channels/wecom/wecom-runtime.mjs +18 -0
  64. package/src/channels/weixin/weixin-api.mjs +98 -2
  65. package/src/channels/weixin/weixin-bridge.mjs +52 -19
  66. package/src/channels/weixin/weixin-controller.mjs +18 -0
  67. package/src/channels/weixin/weixin-runtime.mjs +25 -0
  68. package/src/channels/whatsapp/whatsapp-controller.mjs +20 -0
  69. package/src/channels/whatsapp/whatsapp-runtime.mjs +160 -1
package/lib/client.js CHANGED
@@ -246,6 +246,13 @@ function normalizeError(value, fallbackCode, fallbackMessage) {
246
246
  message: sanitizeMessage(value.message, fallbackMessage)
247
247
  };
248
248
  }
249
+ function normalizeTestMessage(value) {
250
+ if (!isRecord(value)) return null;
251
+ if (value.sent === true) return { sent: true };
252
+ if (value.sent !== false) return null;
253
+ const code = value.code === "test-target-unavailable" ? "test-target-unavailable" : "test-message-failed";
254
+ return { sent: false, code };
255
+ }
249
256
  function unwrapRpcResult(result) {
250
257
  if (!isRecord(result) || typeof result.ok !== "boolean") {
251
258
  throw new Error("\u9489\u9489\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
@@ -340,9 +347,17 @@ function normalizeSnapshot(value) {
340
347
  configured: bots.length,
341
348
  connected: bots.filter((bot) => bot.connected).length
342
349
  },
343
- provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null
350
+ provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
351
+ testMessage: normalizeTestMessage(source.testMessage)
344
352
  };
345
353
  }
354
+ function connectionTestFeedback(result) {
355
+ if (result?.sent === true) return "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\u3002";
356
+ if (result?.code === "test-target-unavailable") {
357
+ return "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002\u673A\u5668\u4EBA\u5C1A\u672A\u6536\u5230\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u6D88\u606F\u3002";
358
+ }
359
+ return result ? "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002" : null;
360
+ }
346
361
  function presentError(error) {
347
362
  return {
348
363
  code: safeErrorCode(error?.code, "DINGTALK_ERROR"),
@@ -436,6 +451,19 @@ var EN = Object.freeze({
436
451
  "\u521A\u521A": "Just now",
437
452
  "\u68C0\u67E5\u8FDE\u63A5": "Check connection",
438
453
  "\u68C0\u67E5\u4E2D\u2026": "Checking\u2026",
454
+ "\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002": "Connection check failed. Try again later.",
455
+ "\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\uFF0C\u8BF7\u5230\u5BF9\u5E94\u673A\u5668\u4EBA\u4F1A\u8BDD\u4E2D\u786E\u8BA4\u3002": "Test message sent. Check the matching bot conversation.",
456
+ "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002\u673A\u5668\u4EBA\u5C1A\u672A\u6536\u5230\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u6D88\u606F\u3002": "Connection check completed. The bot has not received a direct message it can use for testing.",
457
+ "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002": "Connection check completed, but the test message could not be sent.",
458
+ "\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\uFF0C\u8BF7\u5230\u98DE\u4E66\u4F1A\u8BDD\u4E2D\u786E\u8BA4\u3002": "Test message sent. Check the Feishu conversation.",
459
+ "\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\uFF0C\u8BF7\u5230 WhatsApp \u81EA\u804A\u4F1A\u8BDD\u4E2D\u786E\u8BA4\u3002": "Test message sent. Check the WhatsApp self-chat.",
460
+ "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684 WhatsApp \u81EA\u804A\u76EE\u6807\u3002": "Connection check completed, but no WhatsApp self-chat target is available.",
461
+ "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\u3002": "DingTalk connection check completed and the test message was sent.",
462
+ "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002": "DingTalk connection check completed, but the test message could not be sent.",
463
+ "\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\u3002": "WeChat connection check completed and the test message was sent.",
464
+ "\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002": "WeChat connection check completed, but the test message could not be sent.",
465
+ "\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\u3002": "WeCom connection check completed and the test message was sent.",
466
+ "\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002": "WeCom connection check completed, but the test message could not be sent.",
439
467
  "\u91CD\u8BD5\u8FDE\u63A5": "Reconnect",
440
468
  "\u91CD\u8BD5\u4E2D\u2026": "Retrying\u2026",
441
469
  "\u79FB\u9664\u63A5\u5165": "Remove connection",
@@ -1735,6 +1763,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
1735
1763
  function AccountCard({
1736
1764
  account,
1737
1765
  busy,
1766
+ feedback,
1738
1767
  removing,
1739
1768
  onReconnect,
1740
1769
  onWorkspaceSave,
@@ -1798,6 +1827,10 @@ function AccountCard({
1798
1827
  "div",
1799
1828
  { className: "ddt-accountFooter dim-cardFooter" },
1800
1829
  summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
1830
+ feedback ? h2("div", {
1831
+ className: "ddt-summary dim-cardSummary",
1832
+ role: "status"
1833
+ }, feedback) : null,
1801
1834
  h2(
1802
1835
  "div",
1803
1836
  { className: "ddt-actions dim-cardActions" },
@@ -1837,6 +1870,7 @@ function AccountList(props) {
1837
1870
  h2(AccountCard, {
1838
1871
  account,
1839
1872
  busy: props.busyByBot[account.botId],
1873
+ feedback: props.feedbackByBot[account.botId]?.message,
1840
1874
  removing: props.removeTarget === account.botId,
1841
1875
  onReconnect: () => props.onReconnect(account),
1842
1876
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
@@ -1859,6 +1893,7 @@ function DingtalkSettingsTab({ rpcCall }) {
1859
1893
  const [provision, setProvision] = React7.useState(null);
1860
1894
  const [busy, setBusy] = React7.useState(false);
1861
1895
  const [busyByBot, setBusyByBot] = React7.useState({});
1896
+ const [feedbackByBot, setFeedbackByBot] = React7.useState({});
1862
1897
  const [removeTarget, setRemoveTarget] = React7.useState(null);
1863
1898
  const [credentialOpen, setCredentialOpen] = React7.useState(false);
1864
1899
  const [credentialError, setCredentialError] = React7.useState(null);
@@ -1900,6 +1935,21 @@ function DingtalkSettingsTab({ rpcCall }) {
1900
1935
  });
1901
1936
  }
1902
1937
  }, []);
1938
+ const discardStaleFeedback = React7.useCallback((snapshot) => {
1939
+ const botsById = new Map(snapshot.bots.map((bot) => [bot.botId, bot]));
1940
+ setFeedbackByBot((current) => {
1941
+ let changed = false;
1942
+ const next = { ...current };
1943
+ for (const [botId, feedback] of Object.entries(next)) {
1944
+ const bot = botsById.get(botId);
1945
+ if (!bot || feedback.clearWhenDisconnected && (!bot.connected || bot.error)) {
1946
+ delete next[botId];
1947
+ changed = true;
1948
+ }
1949
+ }
1950
+ return changed ? next : current;
1951
+ });
1952
+ }, []);
1903
1953
  const focusAddButton = React7.useCallback(() => {
1904
1954
  if (!mountedRef.current) return;
1905
1955
  if (focusFrameRef.current !== null) window.cancelAnimationFrame(focusFrameRef.current);
@@ -1936,6 +1986,7 @@ function DingtalkSettingsTab({ rpcCall }) {
1936
1986
  revision: snapshot.revision,
1937
1987
  error: null
1938
1988
  });
1989
+ discardStaleFeedback(snapshot);
1939
1990
  if (restoreProvisioning && snapshot.provisioning) {
1940
1991
  setProvision((current) => !current || current.attemptId === snapshot.provisioning.attemptId ? {
1941
1992
  ...current,
@@ -1953,7 +2004,7 @@ function DingtalkSettingsTab({ rpcCall }) {
1953
2004
  }));
1954
2005
  return void 0;
1955
2006
  }
1956
- }, [invoke, workspaceFence]);
2007
+ }, [discardStaleFeedback, invoke, workspaceFence]);
1957
2008
  React7.useEffect(() => {
1958
2009
  const controller = new AbortController();
1959
2010
  void loadStatus({ signal: controller.signal, restoreProvisioning: true });
@@ -2042,6 +2093,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2042
2093
  revision: snapshot.revision,
2043
2094
  error: null
2044
2095
  });
2096
+ discardStaleFeedback(snapshot);
2045
2097
  }
2046
2098
  setCredentialOpen(false);
2047
2099
  announce("\u9489\u9489\u673A\u5668\u4EBA\u51ED\u636E\u5DF2\u7ED1\u5B9A\u3002");
@@ -2052,7 +2104,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2052
2104
  if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
2053
2105
  if (mountedRef.current) setBusy(false);
2054
2106
  }
2055
- }, [announce, invoke, loadStatus, workspaceFence]);
2107
+ }, [announce, discardStaleFeedback, invoke, loadStatus, workspaceFence]);
2056
2108
  const cancelProvisioning = React7.useCallback(async () => {
2057
2109
  if (!mountedRef.current) return;
2058
2110
  setBusy(true);
@@ -2143,6 +2195,13 @@ function DingtalkSettingsTab({ rpcCall }) {
2143
2195
  if (!mountedRef.current) return void 0;
2144
2196
  const snapshotVersion = workspaceFence.beginMutation();
2145
2197
  setBotBusy(account.botId, operation);
2198
+ if (operation === "reconnect") {
2199
+ setFeedbackByBot((current) => {
2200
+ const next = { ...current };
2201
+ delete next[account.botId];
2202
+ return next;
2203
+ });
2204
+ }
2146
2205
  try {
2147
2206
  const snapshot = normalizeSnapshot(await invoke(endpoint, payload));
2148
2207
  if (!mountedRef.current) return void 0;
@@ -2154,25 +2213,47 @@ function DingtalkSettingsTab({ rpcCall }) {
2154
2213
  revision: snapshot.revision,
2155
2214
  error: null
2156
2215
  });
2216
+ discardStaleFeedback(snapshot);
2157
2217
  }
2158
- announce(typeof success === "function" ? success(snapshot) : success);
2218
+ const successMessage = typeof success === "function" ? success(snapshot) : success;
2219
+ if (operation === "reconnect") {
2220
+ setFeedbackByBot((current) => ({
2221
+ ...current,
2222
+ [account.botId]: {
2223
+ message: successMessage,
2224
+ clearWhenDisconnected: snapshot.testMessage?.sent === true
2225
+ }
2226
+ }));
2227
+ }
2228
+ announce(successMessage);
2159
2229
  return snapshot;
2160
2230
  } catch (error) {
2161
2231
  if (!mountedRef.current) return void 0;
2162
- announce(`\u64CD\u4F5C\u5931\u8D25\uFF1A${presentError(error).message}`);
2232
+ const failureMessage = operation === "reconnect" ? "\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002" : `\u64CD\u4F5C\u5931\u8D25\uFF1A${presentError(error).message}`;
2233
+ if (operation === "reconnect") {
2234
+ setFeedbackByBot((current) => ({
2235
+ ...current,
2236
+ [account.botId]: { message: failureMessage, clearWhenDisconnected: false }
2237
+ }));
2238
+ }
2239
+ announce(failureMessage);
2163
2240
  return void 0;
2164
2241
  } finally {
2165
2242
  const shouldRefresh = workspaceFence.endMutation();
2166
2243
  if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true, restoreProvisioning: false });
2167
2244
  if (mountedRef.current) setBotBusy(account.botId, null);
2168
2245
  }
2169
- }, [announce, invoke, loadStatus, setBotBusy, workspaceFence]);
2246
+ }, [announce, discardStaleFeedback, invoke, loadStatus, setBotBusy, workspaceFence]);
2170
2247
  const reconnect = React7.useCallback((account) => runBotAction({
2171
2248
  account,
2172
2249
  operation: "reconnect",
2173
2250
  endpoint: DINGTALK_ENDPOINTS.reconnectBot,
2174
- payload: { botId: account.botId },
2175
- success: (snapshot) => snapshot?.bots.find((bot) => bot.botId === account.botId)?.connected ? "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002" : "\u9489\u9489\u4ECD\u672A\u8FDE\u63A5\uFF0C\u63D2\u4EF6\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5\u3002"
2251
+ payload: { botId: account.botId, sendTest: true },
2252
+ success: (snapshot) => {
2253
+ const refreshed = snapshot?.bots.find((bot) => bot.botId === account.botId);
2254
+ if (!refreshed?.connected) return "\u9489\u9489\u4ECD\u672A\u8FDE\u63A5\uFF0C\u63D2\u4EF6\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5\u3002";
2255
+ return connectionTestFeedback(snapshot.testMessage) ?? "\u9489\u9489\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002";
2256
+ }
2176
2257
  }), [runBotAction]);
2177
2258
  const saveWorkspace = React7.useCallback(async (account, workspace) => {
2178
2259
  const workspaceVersion = workspaceFence.beginMutation();
@@ -2190,13 +2271,14 @@ function DingtalkSettingsTab({ rpcCall }) {
2190
2271
  revision: snapshot.revision,
2191
2272
  error: null
2192
2273
  });
2274
+ discardStaleFeedback(snapshot);
2193
2275
  }
2194
2276
  } finally {
2195
2277
  const shouldRefresh = workspaceFence.endMutation();
2196
2278
  if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
2197
2279
  if (mountedRef.current) setBotBusy(account.botId, null);
2198
2280
  }
2199
- }, [invoke, loadStatus, setBotBusy, workspaceFence]);
2281
+ }, [discardStaleFeedback, invoke, loadStatus, setBotBusy, workspaceFence]);
2200
2282
  const remove = React7.useCallback(async (account) => {
2201
2283
  const snapshot = await runBotAction({
2202
2284
  account,
@@ -2287,6 +2369,7 @@ function DingtalkSettingsTab({ rpcCall }) {
2287
2369
  model.bots.length > 0 ? h2(AccountList, {
2288
2370
  bots: model.bots,
2289
2371
  busyByBot,
2372
+ feedbackByBot,
2290
2373
  removeTarget,
2291
2374
  onReconnect: (account) => void reconnect(account),
2292
2375
  onWorkspaceSave: saveWorkspace,
@@ -2411,6 +2494,13 @@ function checkedTime2(value) {
2411
2494
  return "\u521A\u521A";
2412
2495
  }
2413
2496
  }
2497
+ function connectionTestNotice(value) {
2498
+ if (value?.testMessage?.sent === true) return "\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\uFF0C\u8BF7\u5230\u5BF9\u5E94\u673A\u5668\u4EBA\u4F1A\u8BDD\u4E2D\u786E\u8BA4\u3002";
2499
+ if (value?.testMessage?.code === "test-target-unavailable") {
2500
+ return "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002\u673A\u5668\u4EBA\u5C1A\u672A\u6536\u5230\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u6D88\u606F\u3002";
2501
+ }
2502
+ return value?.testMessage ? "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002" : null;
2503
+ }
2414
2504
  function createTokenChannelSettings(definition) {
2415
2505
  const {
2416
2506
  channel: channel4,
@@ -2433,7 +2523,7 @@ function createTokenChannelSettings(definition) {
2433
2523
  credentialNoun = "Bot Token",
2434
2524
  emptyActionLabel = "\u586B\u5199 Bot Token"
2435
2525
  } = definition;
2436
- function AccountCard5({ account, busy, removing, onReconnect, onWorkspaceSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
2526
+ function AccountCard5({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
2437
2527
  const state = busy === "reconnect" ? "connecting" : account.state;
2438
2528
  const tone = account.connected ? "success" : state === "error" ? "error" : "warning";
2439
2529
  const stateLabel = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA";
@@ -2495,6 +2585,7 @@ function createTokenChannelSettings(definition) {
2495
2585
  "div",
2496
2586
  { className: "ddt-accountFooter dim-cardFooter" },
2497
2587
  summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
2588
+ testNotice ? h2("div", { className: "ddt-summary dim-cardSummary", role: "status" }, testNotice) : null,
2498
2589
  h2(
2499
2590
  "div",
2500
2591
  { className: "ddt-actions dim-cardActions" },
@@ -2541,6 +2632,7 @@ function createTokenChannelSettings(definition) {
2541
2632
  const [credentialError, setCredentialError] = React8.useState(null);
2542
2633
  const [busy, setBusy] = React8.useState(false);
2543
2634
  const [busyByBot, setBusyByBot] = React8.useState({});
2635
+ const [testNoticeByBot, setTestNoticeByBot] = React8.useState({});
2544
2636
  const [removeTarget, setRemoveTarget] = React8.useState(null);
2545
2637
  const mounted = React8.useRef(true);
2546
2638
  const workspaceFence = useWorkspaceSnapshotFence();
@@ -2619,10 +2711,25 @@ function createTokenChannelSettings(definition) {
2619
2711
  const snapshotVersion = workspaceFence.beginMutation();
2620
2712
  setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
2621
2713
  try {
2622
- const snapshot = api4.normalizeSnapshot(await invoke(endpoint, payload));
2714
+ const value = await invoke(endpoint, payload);
2715
+ const snapshot = api4.normalizeSnapshot(value);
2623
2716
  if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
2624
2717
  setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
2625
2718
  }
2719
+ if (mounted.current && operation === "reconnect") {
2720
+ setTestNoticeByBot((current) => ({
2721
+ ...current,
2722
+ [account.botId]: connectionTestNotice(value)
2723
+ }));
2724
+ }
2725
+ } catch (error) {
2726
+ if (operation !== "reconnect") throw error;
2727
+ if (mounted.current) {
2728
+ setTestNoticeByBot((current) => ({
2729
+ ...current,
2730
+ [account.botId]: "\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"
2731
+ }));
2732
+ }
2626
2733
  } finally {
2627
2734
  const shouldRefresh = workspaceFence.endMutation();
2628
2735
  if (shouldRefresh && mounted.current) void loadStatus({ silent: true });
@@ -2644,12 +2751,13 @@ function createTokenChannelSettings(definition) {
2644
2751
  h2("ul", { className: "ddt-list dim-botList" }, model.bots.map((account) => h2("li", { key: account.botId }, h2(AccountCard5, {
2645
2752
  account,
2646
2753
  busy: busyByBot[account.botId],
2754
+ testNotice: testNoticeByBot[account.botId],
2647
2755
  removing: removeTarget === account.botId,
2648
2756
  onReconnect: () => void botAction(
2649
2757
  account,
2650
2758
  "reconnect",
2651
2759
  endpoints.reconnectBot,
2652
- { botId: account.botId }
2760
+ { botId: account.botId, sendTest: true }
2653
2761
  ),
2654
2762
  onWorkspaceSave: (workspace) => botAction(
2655
2763
  account,
@@ -3946,6 +4054,15 @@ function formatCheckedTime(timestamp7) {
3946
4054
  return "\u521A\u521A";
3947
4055
  }
3948
4056
  }
4057
+ function connectionTestNotice2(value) {
4058
+ if (value?.testMessage?.sent === true) {
4059
+ return "\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\uFF0C\u8BF7\u5230\u98DE\u4E66\u4F1A\u8BDD\u4E2D\u786E\u8BA4\u3002";
4060
+ }
4061
+ if (value?.testMessage?.code === "test-target-unavailable") {
4062
+ return "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002\u673A\u5668\u4EBA\u5C1A\u672A\u6536\u5230\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u6D88\u606F\u3002";
4063
+ }
4064
+ return value?.testMessage ? "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002" : null;
4065
+ }
3949
4066
  function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) {
3950
4067
  const cancelRef = React10.useRef(null);
3951
4068
  const idPart = bot.botId.replace(/[^a-zA-Z0-9_-]/g, "-");
@@ -3988,6 +4105,7 @@ function BotCard({
3988
4105
  connection,
3989
4106
  busy,
3990
4107
  actionError,
4108
+ testNotice,
3991
4109
  removing,
3992
4110
  onReconnect,
3993
4111
  onWorkspaceSave,
@@ -4068,6 +4186,10 @@ function BotCard({
4068
4186
  { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : void 0 },
4069
4187
  summary
4070
4188
  ) : null,
4189
+ testNotice ? h2("div", {
4190
+ className: "bxf-healthSummary dim-cardSummary",
4191
+ role: "status"
4192
+ }, testNotice) : null,
4071
4193
  h2(
4072
4194
  "div",
4073
4195
  { className: "bxf-actions bxf-botActions dim-cardActions" },
@@ -4116,6 +4238,7 @@ function BotList(props) {
4116
4238
  connection: bot,
4117
4239
  busy: props.busyByBot[bot.botId],
4118
4240
  actionError: props.errorsByBot[bot.botId],
4241
+ testNotice: props.testNoticesByBot[bot.botId],
4119
4242
  removing: props.removeTargetId === bot.botId,
4120
4243
  onReconnect: () => props.onReconnect(bot),
4121
4244
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
@@ -4195,6 +4318,7 @@ function FeishuSettingsTab({ rpcCall }) {
4195
4318
  const [credentialError, setCredentialError] = React10.useState(null);
4196
4319
  const [busyByBot, setBusyByBot] = React10.useState({});
4197
4320
  const [errorsByBot, setErrorsByBot] = React10.useState({});
4321
+ const [testNoticesByBot, setTestNoticesByBot] = React10.useState({});
4198
4322
  const [removeTargetId, setRemoveTargetId] = React10.useState(null);
4199
4323
  const [announcement, setAnnouncement] = React10.useState("");
4200
4324
  const [now, setNow] = React10.useState(() => Date.now());
@@ -4460,8 +4584,14 @@ function FeishuSettingsTab({ rpcCall }) {
4460
4584
  const snapshotVersion = workspaceFence.beginMutation();
4461
4585
  setBotBusy(botId, "reconnect");
4462
4586
  setBotError(botId, null);
4587
+ setTestNoticesByBot((current) => {
4588
+ const next = { ...current };
4589
+ delete next[botId];
4590
+ return next;
4591
+ });
4463
4592
  try {
4464
- const snapshot = normalizeBotsSnapshot(await invoke(FEISHU_ENDPOINTS.reconnectBot, { botId }));
4593
+ const value = await invoke(FEISHU_ENDPOINTS.reconnectBot, { botId, sendTest: true });
4594
+ const snapshot = normalizeBotsSnapshot(value);
4465
4595
  if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
4466
4596
  mergeSnapshot(snapshot);
4467
4597
  }
@@ -4473,10 +4603,16 @@ function FeishuSettingsTab({ rpcCall }) {
4473
4603
  error.code = refreshed?.error?.code ?? "FEISHU_BOT_OFFLINE";
4474
4604
  throw error;
4475
4605
  }
4476
- announce(connection.connected ? `${bot.name}\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002` : `${bot.name}\u5DF2\u91CD\u65B0\u8FDE\u63A5\u3002`);
4606
+ const testNotice = connectionTestNotice2(value);
4607
+ if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
4608
+ setTestNoticesByBot((current) => ({ ...current, [botId]: testNotice }));
4609
+ }
4610
+ announce(testNotice ?? (connection.connected ? `${bot.name}\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002` : `${bot.name}\u5DF2\u91CD\u65B0\u8FDE\u63A5\u3002`));
4477
4611
  } catch (error) {
4478
- setBotError(botId, error);
4479
- announce(`${bot.name}\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u673A\u5668\u4EBA\u72B6\u6001\u3002`);
4612
+ const failure = new Error("\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002");
4613
+ failure.code = error?.code;
4614
+ setBotError(botId, failure);
4615
+ announce(failure.message);
4480
4616
  } finally {
4481
4617
  const shouldRefresh = workspaceFence.endMutation();
4482
4618
  if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
@@ -4625,6 +4761,7 @@ function FeishuSettingsTab({ rpcCall }) {
4625
4761
  bots: model.bots,
4626
4762
  busyByBot,
4627
4763
  errorsByBot,
4764
+ testNoticesByBot,
4628
4765
  removeTargetId,
4629
4766
  onReconnect: (bot) => void reconnectOneBot(bot),
4630
4767
  onWorkspaceSave: saveWorkspace,
@@ -4652,6 +4789,7 @@ var QQ_ENDPOINTS = Object.freeze({
4652
4789
  });
4653
4790
  var PROVISION_STATES2 = /* @__PURE__ */ new Set(["starting", "pending", "refreshing", "connecting", "connected", "failed", "cancelled"]);
4654
4791
  var ACCOUNT_STATES3 = /* @__PURE__ */ new Set(["connected", "connecting", "offline", "error"]);
4792
+ var TEST_MESSAGE_CODES = /* @__PURE__ */ new Set(["test-target-unavailable", "test-message-failed"]);
4655
4793
  var QR_DATA_URL2 = /^data:image\/(?:png|webp);base64,[a-z\d+/]+={0,2}$/i;
4656
4794
  function isRecord4(value) {
4657
4795
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -4725,17 +4863,35 @@ function normalizeBot3(value) {
4725
4863
  } : null
4726
4864
  };
4727
4865
  }
4866
+ function normalizeTestMessage2(value) {
4867
+ if (!isRecord4(value) || typeof value.sent !== "boolean") return void 0;
4868
+ if (value.sent) return { sent: true };
4869
+ const code = text2(value.code, "test-message-failed", 80);
4870
+ return {
4871
+ sent: false,
4872
+ code: TEST_MESSAGE_CODES.has(code) ? code : "test-message-failed"
4873
+ };
4874
+ }
4728
4875
  function normalizeSnapshot3(value) {
4729
4876
  const source = isRecord4(value?.snapshot) ? value.snapshot : value;
4730
4877
  if (!isRecord4(source) || !Array.isArray(source.bots)) throw new Error("QQ \u670D\u52A1\u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u673A\u5668\u4EBA\u5217\u8868");
4731
4878
  const bots = source.bots.map(normalizeBot3).filter(Boolean);
4879
+ const testMessage = normalizeTestMessage2(source.testMessage);
4732
4880
  return {
4733
4881
  revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
4734
4882
  bots,
4735
4883
  totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
4736
- provisioning: source.provisioning ? normalizeProvisioning3(source.provisioning) : null
4884
+ provisioning: source.provisioning ? normalizeProvisioning3(source.provisioning) : null,
4885
+ ...testMessage ? { testMessage } : {}
4737
4886
  };
4738
4887
  }
4888
+ function connectionTestFeedback2(result) {
4889
+ if (result?.sent === true) return "\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\uFF0C\u8BF7\u5230\u5BF9\u5E94\u673A\u5668\u4EBA\u4F1A\u8BDD\u4E2D\u786E\u8BA4\u3002";
4890
+ if (result?.code === "test-target-unavailable") {
4891
+ return "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002\u673A\u5668\u4EBA\u5C1A\u672A\u6536\u5230\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u6D88\u606F\u3002";
4892
+ }
4893
+ return result ? "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002" : null;
4894
+ }
4739
4895
  function presentError4(error) {
4740
4896
  return {
4741
4897
  code: text2(error?.code, "QQ_ERROR", 80),
@@ -4983,6 +5139,7 @@ function RemoveConfirmation3({ account, busy, onConfirm, onCancel }) {
4983
5139
  function AccountCard2({
4984
5140
  account,
4985
5141
  busy,
5142
+ feedback,
4986
5143
  removing,
4987
5144
  onReconnect,
4988
5145
  onWorkspaceSave,
@@ -5035,6 +5192,11 @@ function AccountCard2({
5035
5192
  "div",
5036
5193
  { className: "ddt-accountFooter dim-cardFooter" },
5037
5194
  summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
5195
+ feedback ? h2("div", {
5196
+ className: "ddt-summary dim-cardSummary",
5197
+ role: "status",
5198
+ "aria-live": "polite"
5199
+ }, feedback) : null,
5038
5200
  h2(
5039
5201
  "div",
5040
5202
  { className: "ddt-actions dim-cardActions" },
@@ -5056,6 +5218,7 @@ function QqSettingsTab({ rpcCall }) {
5056
5218
  const [provision, setProvision] = React11.useState(null);
5057
5219
  const [busy, setBusy] = React11.useState(false);
5058
5220
  const [busyByBot, setBusyByBot] = React11.useState({});
5221
+ const [feedbackByBot, setFeedbackByBot] = React11.useState({});
5059
5222
  const [removeTarget, setRemoveTarget] = React11.useState(null);
5060
5223
  const [credentialOpen, setCredentialOpen] = React11.useState(false);
5061
5224
  const [credentialError, setCredentialError] = React11.useState(null);
@@ -5216,6 +5379,34 @@ function QqSettingsTab({ rpcCall }) {
5216
5379
  });
5217
5380
  }
5218
5381
  }, [invoke, loadStatus, workspaceFence]);
5382
+ const reconnect = React11.useCallback(async (account) => {
5383
+ setFeedbackByBot((current) => {
5384
+ const next = { ...current };
5385
+ delete next[account.botId];
5386
+ return next;
5387
+ });
5388
+ try {
5389
+ const snapshot = await botAction(
5390
+ account,
5391
+ "reconnect",
5392
+ QQ_ENDPOINTS.reconnectBot,
5393
+ { botId: account.botId, sendTest: true }
5394
+ );
5395
+ if (mounted.current) {
5396
+ setFeedbackByBot((current) => ({
5397
+ ...current,
5398
+ [account.botId]: connectionTestFeedback2(snapshot?.testMessage)
5399
+ }));
5400
+ }
5401
+ } catch {
5402
+ if (mounted.current) {
5403
+ setFeedbackByBot((current) => ({
5404
+ ...current,
5405
+ [account.botId]: "\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"
5406
+ }));
5407
+ }
5408
+ }
5409
+ }, [botAction]);
5219
5410
  let provisionView = null;
5220
5411
  if (provision?.status === "starting") provisionView = h2("div", { className: "ddt-card ddt-loading dim-surfaceCard" }, h2("div", { className: "ddt-spinner" }), "\u6B63\u5728\u7533\u8BF7 QQ \u4E8C\u7EF4\u7801\u2026");
5221
5412
  else if (["pending", "refreshing"].includes(provision?.status)) provisionView = h2(QrPanel2, {
@@ -5242,8 +5433,9 @@ function QqSettingsTab({ rpcCall }) {
5242
5433
  h2("ul", { className: "ddt-list dim-botList" }, model.bots.map((account) => h2("li", { key: account.botId }, h2(AccountCard2, {
5243
5434
  account,
5244
5435
  busy: busyByBot[account.botId],
5436
+ feedback: feedbackByBot[account.botId],
5245
5437
  removing: removeTarget === account.botId,
5246
- onReconnect: () => void botAction(account, "reconnect", QQ_ENDPOINTS.reconnectBot, { botId: account.botId }),
5438
+ onReconnect: () => void reconnect(account),
5247
5439
  onWorkspaceSave: (workspace) => botAction(
5248
5440
  account,
5249
5441
  "workspace",
@@ -5329,6 +5521,7 @@ oauth_config:
5329
5521
  bot:
5330
5522
  - app_mentions:read
5331
5523
  - chat:write
5524
+ - files:read
5332
5525
  - im:history
5333
5526
  settings:
5334
5527
  event_subscriptions:
@@ -5596,6 +5789,13 @@ function timestamp4(value) {
5596
5789
  const parsed = typeof value === "string" ? Date.parse(value) : Number.NaN;
5597
5790
  return Number.isNaN(parsed) ? void 0 : parsed;
5598
5791
  }
5792
+ function normalizeTestMessage3(value) {
5793
+ if (!isRecord5(value)) return null;
5794
+ if (value.sent === true) return { sent: true };
5795
+ if (value.sent !== false) return null;
5796
+ const code = value.code === "test-target-unavailable" ? "test-target-unavailable" : "test-message-failed";
5797
+ return { sent: false, code };
5798
+ }
5599
5799
  function unwrapRpcResult7(result) {
5600
5800
  if (!isRecord5(result) || typeof result.ok !== "boolean") throw new Error("\u4F01\u4E1A\u5FAE\u4FE1\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
5601
5801
  if (!result.ok) {
@@ -5661,7 +5861,8 @@ function normalizeSnapshot6(value) {
5661
5861
  revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
5662
5862
  bots,
5663
5863
  totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
5664
- provisioning: source.provisioning ? normalizeProvisioning4(source.provisioning) : null
5864
+ provisioning: source.provisioning ? normalizeProvisioning4(source.provisioning) : null,
5865
+ testMessage: normalizeTestMessage3(source.testMessage)
5665
5866
  };
5666
5867
  }
5667
5868
  function presentError7(error) {
@@ -5911,6 +6112,7 @@ function RemoveConfirmation4({ account, busy, onConfirm, onCancel }) {
5911
6112
  function AccountCard3({
5912
6113
  account,
5913
6114
  busy,
6115
+ feedback,
5914
6116
  removing,
5915
6117
  onReconnect,
5916
6118
  onWorkspaceSave,
@@ -5963,6 +6165,11 @@ function AccountCard3({
5963
6165
  "div",
5964
6166
  { className: "ddt-accountFooter dim-cardFooter" },
5965
6167
  summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
6168
+ feedback ? h2("div", {
6169
+ className: "ddt-summary dim-cardSummary",
6170
+ role: "status",
6171
+ "aria-live": "polite"
6172
+ }, feedback) : null,
5966
6173
  h2(
5967
6174
  "div",
5968
6175
  { className: "ddt-actions dim-cardActions" },
@@ -5984,19 +6191,40 @@ function WecomSettingsTab({ rpcCall }) {
5984
6191
  const [provision, setProvision] = React13.useState(null);
5985
6192
  const [busy, setBusy] = React13.useState(false);
5986
6193
  const [busyByBot, setBusyByBot] = React13.useState({});
6194
+ const [feedbackByBot, setFeedbackByBot] = React13.useState({});
5987
6195
  const [removeTarget, setRemoveTarget] = React13.useState(null);
5988
6196
  const [credentialOpen, setCredentialOpen] = React13.useState(false);
5989
6197
  const [credentialError, setCredentialError] = React13.useState(null);
6198
+ const [notice, setNotice] = React13.useState("");
5990
6199
  const [now, setNow] = React13.useState(Date.now());
5991
6200
  const mounted = React13.useRef(true);
5992
6201
  const workspaceFence = useWorkspaceSnapshotFence();
5993
6202
  const addButtonRef = React13.useRef(null);
6203
+ const noticeFrameRef = React13.useRef(null);
6204
+ const announce = React13.useCallback((message) => {
6205
+ if (!mounted.current) return;
6206
+ if (noticeFrameRef.current !== null) {
6207
+ window.cancelAnimationFrame(noticeFrameRef.current);
6208
+ noticeFrameRef.current = null;
6209
+ }
6210
+ setNotice("");
6211
+ if (message) {
6212
+ noticeFrameRef.current = window.requestAnimationFrame(() => {
6213
+ noticeFrameRef.current = null;
6214
+ if (mounted.current) setNotice(message);
6215
+ });
6216
+ }
6217
+ }, []);
5994
6218
  React13.useEffect(() => {
5995
6219
  const disposeDingtalk = installDingtalkStyles();
5996
6220
  const disposeWecom = installWecomStyles();
5997
6221
  mounted.current = true;
5998
6222
  return () => {
5999
6223
  mounted.current = false;
6224
+ if (noticeFrameRef.current !== null) {
6225
+ window.cancelAnimationFrame(noticeFrameRef.current);
6226
+ noticeFrameRef.current = null;
6227
+ }
6000
6228
  disposeWecom();
6001
6229
  disposeDingtalk();
6002
6230
  };
@@ -6144,6 +6372,45 @@ function WecomSettingsTab({ rpcCall }) {
6144
6372
  });
6145
6373
  }
6146
6374
  }, [invoke, loadStatus, workspaceFence]);
6375
+ const reconnect = React13.useCallback(async (account) => {
6376
+ setFeedbackByBot((current) => {
6377
+ const next = { ...current };
6378
+ delete next[account.botId];
6379
+ return next;
6380
+ });
6381
+ try {
6382
+ const snapshot = await botAction(
6383
+ account,
6384
+ "reconnect",
6385
+ WECOM_ENDPOINTS.reconnectBot,
6386
+ { botId: account.botId, sendTest: true }
6387
+ );
6388
+ if (!snapshot) return;
6389
+ const refreshed = snapshot.bots.find((bot) => bot.botId === account.botId);
6390
+ let feedback;
6391
+ if (!refreshed?.connected) {
6392
+ feedback = "\u4F01\u4E1A\u5FAE\u4FE1\u4ECD\u672A\u8FDE\u63A5\uFF0C\u63D2\u4EF6\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5\u3002";
6393
+ } else if (snapshot.testMessage?.sent) {
6394
+ feedback = "\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\u3002";
6395
+ } else if (snapshot.testMessage?.code === "test-target-unavailable") {
6396
+ feedback = "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002\u673A\u5668\u4EBA\u5C1A\u672A\u6536\u5230\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u6D88\u606F\u3002";
6397
+ } else if (snapshot.testMessage) {
6398
+ feedback = "\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002";
6399
+ } else {
6400
+ feedback = "\u4F01\u4E1A\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002";
6401
+ }
6402
+ if (mounted.current) {
6403
+ setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
6404
+ }
6405
+ announce(feedback);
6406
+ } catch {
6407
+ const feedback = "\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
6408
+ if (mounted.current) {
6409
+ setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
6410
+ }
6411
+ announce(feedback);
6412
+ }
6413
+ }, [announce, botAction]);
6147
6414
  let provisionView = null;
6148
6415
  if (provision?.status === "starting") provisionView = h2("div", { className: "ddt-card ddt-loading dim-surfaceCard" }, h2("div", { className: "ddt-spinner" }), "\u6B63\u5728\u7533\u8BF7\u4F01\u4E1A\u5FAE\u4FE1\u4E8C\u7EF4\u7801\u2026");
6149
6416
  else if (["pending", "refreshing"].includes(provision?.status)) provisionView = h2(QrPanel3, {
@@ -6170,8 +6437,9 @@ function WecomSettingsTab({ rpcCall }) {
6170
6437
  h2("ul", { className: "ddt-list dim-botList" }, model.bots.map((account) => h2("li", { key: account.botId }, h2(AccountCard3, {
6171
6438
  account,
6172
6439
  busy: busyByBot[account.botId],
6440
+ feedback: feedbackByBot[account.botId],
6173
6441
  removing: removeTarget === account.botId,
6174
- onReconnect: () => void botAction(account, "reconnect", WECOM_ENDPOINTS.reconnectBot, { botId: account.botId }),
6442
+ onReconnect: () => void reconnect(account),
6175
6443
  onWorkspaceSave: (workspace) => botAction(
6176
6444
  account,
6177
6445
  "workspace",
@@ -6215,6 +6483,7 @@ function WecomSettingsTab({ rpcCall }) {
6215
6483
  credentialOpen,
6216
6484
  addButtonRef
6217
6485
  }),
6486
+ h2("div", { className: "ddt-visuallyHidden", role: "status", "aria-live": "polite" }, notice),
6218
6487
  model.phase === "loading" ? h2(LoadingView4) : model.phase === "error" ? h2("div", { className: "ddt-card dim-surfaceCard" }, h2("div", { className: "ddt-inlineError dim-inlineError" }, h2("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u72B6\u6001"), h2("p", null, model.error?.message), h2(Button9, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6"))) : h2(
6219
6488
  React13.Fragment,
6220
6489
  null,
@@ -6262,6 +6531,13 @@ function string(value, fallback = "") {
6262
6531
  function timestamp5(value) {
6263
6532
  return typeof value === "number" && Number.isFinite(value) ? value : null;
6264
6533
  }
6534
+ function normalizeTestMessage4(value) {
6535
+ if (!isRecord6(value)) return null;
6536
+ if (value.sent === true) return { sent: true };
6537
+ if (value.sent !== false) return null;
6538
+ const code = value.code === "test-target-unavailable" ? "test-target-unavailable" : "test-message-failed";
6539
+ return { sent: false, code };
6540
+ }
6265
6541
  function unwrapRpcResult8(result) {
6266
6542
  if (!isRecord6(result) || typeof result.ok !== "boolean") {
6267
6543
  throw new Error("\u5FAE\u4FE1\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
@@ -6355,7 +6631,8 @@ function normalizeSnapshot7(value) {
6355
6631
  configured: bots.length,
6356
6632
  connected: bots.filter((bot) => bot.connected).length
6357
6633
  },
6358
- provisioning: value.provisioning ? normalizeProvisioning5(value.provisioning) : null
6634
+ provisioning: value.provisioning ? normalizeProvisioning5(value.provisioning) : null,
6635
+ testMessage: normalizeTestMessage4(value.testMessage)
6359
6636
  };
6360
6637
  }
6361
6638
  function presentError8(error) {
@@ -6735,6 +7012,7 @@ function checkedTime5(timestamp7) {
6735
7012
  function AccountCard4({
6736
7013
  account,
6737
7014
  busy,
7015
+ feedback,
6738
7016
  removing,
6739
7017
  onReconnect,
6740
7018
  onWorkspaceSave,
@@ -6792,6 +7070,11 @@ function AccountCard4({
6792
7070
  "div",
6793
7071
  { className: "dxw-accountFooter dim-cardFooter" },
6794
7072
  summary ? h2("div", { className: "dxw-summary dim-cardSummary" }, summary) : null,
7073
+ feedback ? h2("div", {
7074
+ className: "dxw-summary dim-cardSummary",
7075
+ role: "status",
7076
+ "aria-live": "polite"
7077
+ }, feedback) : null,
6795
7078
  h2(
6796
7079
  "div",
6797
7080
  { className: "dxw-actions dim-cardActions" },
@@ -6833,6 +7116,7 @@ function AccountList2(props) {
6833
7116
  h2(AccountCard4, {
6834
7117
  account,
6835
7118
  busy: props.busyByBot[account.botId],
7119
+ feedback: props.feedbackByBot[account.botId],
6836
7120
  removing: props.removeTarget === account.botId,
6837
7121
  onReconnect: () => props.onReconnect(account),
6838
7122
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
@@ -6864,6 +7148,7 @@ function WeixinSettingsTab({ rpcCall }) {
6864
7148
  const [provision, setProvision] = React14.useState(null);
6865
7149
  const [busy, setBusy] = React14.useState(false);
6866
7150
  const [busyByBot, setBusyByBot] = React14.useState({});
7151
+ const [feedbackByBot, setFeedbackByBot] = React14.useState({});
6867
7152
  const [removeTarget, setRemoveTarget] = React14.useState(null);
6868
7153
  const [notice, setNotice] = React14.useState("");
6869
7154
  const [now, setNow] = React14.useState(() => Date.now());
@@ -7062,15 +7347,42 @@ function WeixinSettingsTab({ rpcCall }) {
7062
7347
  const reconnect = React14.useCallback(async (account) => {
7063
7348
  const snapshotVersion = workspaceFence.beginMutation();
7064
7349
  setBotBusy(account.botId, "reconnect");
7350
+ setFeedbackByBot((current) => {
7351
+ const next = { ...current };
7352
+ delete next[account.botId];
7353
+ return next;
7354
+ });
7065
7355
  try {
7066
- const snapshot = normalizeSnapshot7(await invoke(WEIXIN_ENDPOINTS.reconnectBot, { botId: account.botId }));
7356
+ const snapshot = normalizeSnapshot7(await invoke(
7357
+ WEIXIN_ENDPOINTS.reconnectBot,
7358
+ { botId: account.botId, sendTest: true }
7359
+ ));
7067
7360
  if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
7068
7361
  setModel((current) => ({ ...current, bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision }));
7069
7362
  }
7070
7363
  const refreshed = snapshot.bots.find((bot) => bot.botId === account.botId);
7071
- announce(refreshed?.connected ? "\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002" : "\u5FAE\u4FE1\u4ECD\u672A\u8FDE\u63A5\uFF0C\u63D2\u4EF6\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5\u3002");
7072
- } catch (error) {
7073
- announce(`\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF1A${presentError8(error).message}`);
7364
+ let feedback;
7365
+ if (!refreshed?.connected) {
7366
+ feedback = "\u5FAE\u4FE1\u4ECD\u672A\u8FDE\u63A5\uFF0C\u63D2\u4EF6\u4F1A\u7EE7\u7EED\u81EA\u52A8\u91CD\u8BD5\u3002";
7367
+ } else if (snapshot.testMessage?.sent) {
7368
+ feedback = "\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\u3002";
7369
+ } else if (snapshot.testMessage?.code === "test-target-unavailable") {
7370
+ feedback = "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002\u673A\u5668\u4EBA\u5C1A\u672A\u6536\u5230\u53EF\u7528\u4E8E\u6D4B\u8BD5\u7684\u79C1\u804A\u6D88\u606F\u3002";
7371
+ } else if (snapshot.testMessage) {
7372
+ feedback = "\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002";
7373
+ } else {
7374
+ feedback = "\u5FAE\u4FE1\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\u3002";
7375
+ }
7376
+ if (mountedRef.current) {
7377
+ setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
7378
+ }
7379
+ announce(feedback);
7380
+ } catch {
7381
+ const feedback = "\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
7382
+ if (mountedRef.current) {
7383
+ setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
7384
+ }
7385
+ announce(feedback);
7074
7386
  } finally {
7075
7387
  const shouldRefresh = workspaceFence.endMutation();
7076
7388
  if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
@@ -7183,6 +7495,7 @@ function WeixinSettingsTab({ rpcCall }) {
7183
7495
  model.bots.length > 0 ? h2(AccountList2, {
7184
7496
  bots: model.bots,
7185
7497
  busyByBot,
7498
+ feedbackByBot,
7186
7499
  removeTarget,
7187
7500
  onReconnect: (account) => void reconnect(account),
7188
7501
  onWorkspaceSave: saveWorkspace,
@@ -7353,6 +7666,15 @@ function checkedTime6(value) {
7353
7666
  return "\u521A\u521A";
7354
7667
  }
7355
7668
  }
7669
+ function connectionTestNotice3(value) {
7670
+ if (value?.testMessage?.sent === true) {
7671
+ return "\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001\uFF0C\u8BF7\u5230 WhatsApp \u81EA\u804A\u4F1A\u8BDD\u4E2D\u786E\u8BA4\u3002";
7672
+ }
7673
+ if (value?.testMessage?.code === "test-target-unavailable") {
7674
+ return "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684 WhatsApp \u81EA\u804A\u76EE\u6807\u3002";
7675
+ }
7676
+ return value?.testMessage ? "\u8FDE\u63A5\u68C0\u67E5\u5B8C\u6210\uFF0C\u4F46\u6D4B\u8BD5\u6D88\u606F\u53D1\u9001\u5931\u8D25\u3002" : null;
7677
+ }
7356
7678
  function Heading6({ totals, busy, onAdd, addButtonRef }) {
7357
7679
  return h2(
7358
7680
  "div",
@@ -7541,6 +7863,7 @@ function RemoveConfirmation5({ account, busy, onConfirm, onCancel }) {
7541
7863
  function WhatsappAccountCard({
7542
7864
  account,
7543
7865
  busy,
7866
+ testNotice,
7544
7867
  removing,
7545
7868
  onReconnect,
7546
7869
  onWorkspaceSave,
@@ -7607,6 +7930,10 @@ function WhatsappAccountCard({
7607
7930
  "div",
7608
7931
  { className: "ddt-accountFooter dim-cardFooter" },
7609
7932
  summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
7933
+ testNotice ? h2("div", {
7934
+ className: "ddt-summary dim-cardSummary",
7935
+ role: "status"
7936
+ }, testNotice) : null,
7610
7937
  h2(
7611
7938
  "div",
7612
7939
  { className: "ddt-actions dim-cardActions" },
@@ -7642,6 +7969,7 @@ function WhatsappSettingsTab({ rpcCall }) {
7642
7969
  const [provision, setProvision] = React15.useState(null);
7643
7970
  const [busy, setBusy] = React15.useState(false);
7644
7971
  const [busyByBot, setBusyByBot] = React15.useState({});
7972
+ const [testNoticeByBot, setTestNoticeByBot] = React15.useState({});
7645
7973
  const [removeTarget, setRemoveTarget] = React15.useState(null);
7646
7974
  const [now, setNow] = React15.useState(Date.now());
7647
7975
  const mounted = React15.useRef(true);
@@ -7780,10 +8108,32 @@ function WhatsappSettingsTab({ rpcCall }) {
7780
8108
  const botAction = React15.useCallback(async (account, operation, endpoint, payload) => {
7781
8109
  const snapshotVersion = workspaceFence.beginMutation();
7782
8110
  setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
8111
+ if (operation === "reconnect") {
8112
+ setTestNoticeByBot((current) => {
8113
+ const next = { ...current };
8114
+ delete next[account.botId];
8115
+ return next;
8116
+ });
8117
+ }
7783
8118
  try {
7784
- const snapshot = normalizeSnapshot8(await invoke(endpoint, payload));
8119
+ const value = await invoke(endpoint, payload);
8120
+ const snapshot = normalizeSnapshot8(value);
7785
8121
  if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
7786
8122
  setModel({ phase: "ready", bots: snapshot.bots, totals: snapshot.totals, error: null });
8123
+ if (operation === "reconnect") {
8124
+ setTestNoticeByBot((current) => ({
8125
+ ...current,
8126
+ [account.botId]: connectionTestNotice3(value)
8127
+ }));
8128
+ }
8129
+ }
8130
+ } catch (error) {
8131
+ if (operation !== "reconnect") throw error;
8132
+ if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
8133
+ setTestNoticeByBot((current) => ({
8134
+ ...current,
8135
+ [account.botId]: "\u8FDE\u63A5\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"
8136
+ }));
7787
8137
  }
7788
8138
  } finally {
7789
8139
  const shouldRefresh = workspaceFence.endMutation();
@@ -7806,12 +8156,13 @@ function WhatsappSettingsTab({ rpcCall }) {
7806
8156
  h2("ul", { className: "ddt-list dim-botList" }, model.bots.map((account) => h2("li", { key: account.botId }, h2(WhatsappAccountCard, {
7807
8157
  account,
7808
8158
  busy: busyByBot[account.botId],
8159
+ testNotice: testNoticeByBot[account.botId],
7809
8160
  removing: removeTarget === account.botId,
7810
8161
  onReconnect: () => void botAction(
7811
8162
  account,
7812
8163
  "reconnect",
7813
8164
  WHATSAPP_ENDPOINTS.reconnectBot,
7814
- { botId: account.botId }
8165
+ { botId: account.botId, sendTest: true }
7815
8166
  ),
7816
8167
  onWorkspaceSave: (workspace) => botAction(
7817
8168
  account,