@xmanrui/dsh-im 0.14.0 → 0.16.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/lib/client.js CHANGED
@@ -399,6 +399,7 @@ var EN = Object.freeze({
399
399
  "IM \u6E20\u9053": "IM channels",
400
400
  "\u8BA9 DeepSeek Harness \u89E6\u624B\u53EF\u53CA": "DeepSeek Harness, always within reach",
401
401
  "AI Office": "AI Office",
402
+ "\uFF08\u5B9E\u9A8C\u529F\u80FD\uFF09": "(Experimental)",
402
403
  "AI Office \u8BBE\u7F6E": "AI Office settings",
403
404
  "AI Office \u8BBE\u7F6E\u9875\u7F3A\u5C11 RPC \u8FDE\u63A5": "AI Office settings are missing an RPC connection",
404
405
  "\u6B63\u5728\u8BFB\u53D6 AI Office Connector\u2026": "Loading AI Office Connector\u2026",
@@ -586,6 +587,27 @@ var EN = Object.freeze({
586
587
  "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u65B0\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u5C31\u7EEA": "Keep this page open until the bot connection is ready",
587
588
  "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00": "Open in Feishu",
588
589
  "\u53D6\u6D88\u6DFB\u52A0": "Cancel",
590
+ "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u4FEE\u590D\u5361\u7247\u6309\u94AE": "Scan with Feishu to repair card buttons",
591
+ "\u626B\u7801\u4F1A\u66F4\u65B0\u73B0\u6709\u98DE\u4E66\u5E94\u7528\uFF0C\u53EA\u589E\u91CF\u8865\u5145\u5361\u7247\u6309\u94AE\u56DE\u8C03\uFF1B\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002\u786E\u8BA4\u540E\u6B64\u673A\u5668\u4EBA\u4F1A\u77ED\u6682\u91CD\u8FDE\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u53D7\u5F71\u54CD\u3002": "Scanning updates the existing Feishu app with only the card-button callback. It does not create a new app. This bot reconnects briefly after confirmation; other bots are not affected.",
592
+ "\u6838\u5BF9\u73B0\u6709\u5E94\u7528\u540D\u79F0\uFF0C\u5E76\u786E\u8BA4\u53EA\u65B0\u589E\u5361\u7247\u56DE\u8C03": "Review the existing app name and confirm that only the card callback is added",
593
+ "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u5361\u7247\u6309\u94AE\u4FEE\u590D\u5B8C\u6210": "Keep this page open until card-button repair finishes",
594
+ "\u53D6\u6D88\u4FEE\u590D": "Cancel repair",
595
+ "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u5B8C\u6210\u5361\u7247\u6309\u94AE\u4FEE\u590D": "Confirmed. Finishing card-button repair",
596
+ "\u6B63\u5728\u51C6\u5907\u4FEE\u590D\u4E8C\u7EF4\u7801": "Preparing the repair QR code",
597
+ "\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u9A8C\u8BC1\u5361\u7247\u6309\u94AE\u56DE\u8C03\u5E76\u91CD\u8FDE\u6B64\u673A\u5668\u4EBA\uFF1B\u6B64\u9636\u6BB5\u65E0\u6CD5\u53D6\u6D88\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002": "The update was submitted. Verifying the card callback and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.",
598
+ "\u6B63\u5728\u4E3A\u73B0\u6709\u98DE\u4E66\u5E94\u7528\u7533\u8BF7\u4E00\u6B21\u6027\u66F4\u65B0\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002": "Requesting a one-time update QR code for the existing Feishu app\u2026",
599
+ "\u5361\u7247\u6309\u94AE\u6CA1\u6709\u4FEE\u590D\u5B8C\u6210": "Card-button repair did not finish",
600
+ "\u4FEE\u590D\u5361\u7247\u6309\u94AE": "Repair card buttons",
601
+ "\u7B49\u5F85\u626B\u7801\u2026": "Waiting for scan\u2026",
602
+ "\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u5361\u7247\u4FEE\u590D\u4E8C\u7EF4\u7801": "Feishu returned a repair QR code for a different bot",
603
+ "\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u4FEE\u590D\u4FE1\u606F\u7F3A\u5C11 botId": "Feishu repair status is missing the bot ID",
604
+ "\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u6CE8\u518C\u8FDB\u5EA6": "Feishu returned registration progress for a different operation",
605
+ "\u6B64\u673A\u5668\u4EBA": "this bot",
606
+ '${botName ?? "\u673A\u5668\u4EBA"}\u7684\u4FEE\u590D\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002': 'Repair QR code generated for ${botName ?? "bot"}. Scan it with Feishu.',
607
+ "${targetBot.bot.name}\u5DF2\u8FDE\u63A5\uFF0C\u53EF\u4EE5\u5728\u98DE\u4E66\u4E2D\u5F00\u59CB\u804A\u5929\u3002": "${targetBot.bot.name} is connected and ready to chat in Feishu.",
608
+ "\u5DF2\u53D6\u6D88\u5361\u7247\u6309\u94AE\u4FEE\u590D\u3002": "Card-button repair was cancelled.",
609
+ "\u5361\u7247\u6309\u94AE\u5DF2\u66F4\u65B0\uFF0C\u4F46\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u673A\u5668\u4EBA\u8FDE\u63A5\u72B6\u6001": "The card callback was updated, but the bot connection could not be confirmed yet",
610
+ "\u98DE\u4E66\u5361\u7247\u6309\u94AE\u4FEE\u590D\u5931\u8D25": "Could not repair the Feishu card buttons",
589
611
  "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA": "Confirmed. Connecting the new bot",
590
612
  "\u6B63\u5728\u5B89\u5168\u4FDD\u5B58\u51ED\u636E\u5E76\u68C0\u67E5\u65B0\u673A\u5668\u4EBA\u7684\u6D88\u606F\u901A\u9053\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002": "Saving credentials and checking the new bot connection. Existing bots will not be interrupted.",
591
613
  "\u6B63\u5728\u5411\u98DE\u4E66\u7533\u8BF7\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002": "Requesting a one-time authorization QR code from Feishu\u2026",
@@ -620,6 +642,13 @@ var EN = Object.freeze({
620
642
  "\u8FD9\u662F\u5FAE\u4FE1\u9644\u52A0\u7684\u5B89\u5168\u786E\u8BA4\u6B65\u9AA4\u3002\u914D\u5BF9\u7801\u53EA\u7528\u4E8E\u672C\u6B21\u626B\u7801\u8F6E\u8BE2\uFF0C\u4E0D\u4F1A\u5199\u5165\u914D\u7F6E\u6216\u65E5\u5FD7\u3002": "This is an additional WeChat confirmation step. The pairing code is used only for this connection and is never stored.",
621
643
  "\u6B63\u5728\u4FDD\u5B58\u51ED\u636E\u5E76\u9A8C\u8BC1 Harness \u4E0E\u5FAE\u4FE1\u957F\u8F6E\u8BE2\u3002": "Saving credentials and verifying the WeChat connection.",
622
644
  "\u5FAE\u4FE1\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u542F\u52A8\u6D88\u606F\u8FDE\u63A5": "Confirmed in WeChat. Starting the message connection",
645
+ "\u5FAE\u4FE1\u5DF2\u6388\u6743\uFF0C\u4F46\u65E0\u6CD5\u8BFB\u53D6\u73B0\u6709\u767B\u5F55\u51ED\u636E\u3002\u8BF7\u68C0\u67E5 DSH \u51ED\u636E\u5B58\u50A8\u3002": "WeChat was authorized, but the existing login credential could not be read. Check the DSH credential store.",
646
+ "\u5FAE\u4FE1\u5DF2\u6388\u6743\uFF0C\u4F46\u767B\u5F55\u51ED\u636E\u65E0\u6CD5\u5199\u5165 DSH \u51ED\u636E\u5B58\u50A8\u3002\u8BF7\u68C0\u67E5\u51ED\u636E\u5B58\u50A8\u662F\u5426\u53EF\u5199\u3002": "WeChat was authorized, but the login credential could not be written to the DSH credential store. Check that the store is writable.",
647
+ "\u5FAE\u4FE1\u5DF2\u6388\u6743\uFF0C\u4F46\u8D26\u53F7\u914D\u7F6E\u65E0\u6CD5\u5199\u5165\u672C\u673A\u3002\u8BF7\u68C0\u67E5 DSH_HOME \u76EE\u5F55\u6743\u9650\u3002": "WeChat was authorized, but the account configuration could not be saved locally. Check the DSH_HOME directory permissions.",
648
+ "\u5FAE\u4FE1\u5DF2\u6388\u6743\uFF0C\u4F46\u65E0\u6CD5\u521D\u59CB\u5316\u8D26\u53F7\u72B6\u6001\u6216\u5DE5\u4F5C\u533A\u3002\u8BF7\u68C0\u67E5 DSH_HOME \u548C\u5DE5\u4F5C\u533A\u76EE\u5F55\u3002": "WeChat was authorized, but the account state or workspace could not be initialized. Check DSH_HOME and the workspace directory.",
649
+ "\u5FAE\u4FE1\u5DF2\u6388\u6743\uFF0C\u4F46\u63D2\u4EF6\u65E0\u6CD5\u8FDE\u63A5\u672C\u673A Harness\u3002\u8BF7\u786E\u8BA4 dsh web \u5DF2\u6B63\u5E38\u542F\u52A8\u3002": "WeChat was authorized, but the plugin could not reach the local Harness. Confirm that dsh web is running normally.",
650
+ "\u5FAE\u4FE1\u5DF2\u6388\u6743\uFF0C\u4F46\u6D88\u606F\u8FDE\u63A5\u521D\u59CB\u5316\u5931\u8D25\u3002\u8BF7\u67E5\u770B dsh web \u65E5\u5FD7\u540E\u91CD\u8BD5\u3002": "WeChat was authorized, but the message connection could not be initialized. Check the dsh web logs and try again.",
651
+ "\u5FAE\u4FE1\u5DF2\u6388\u6743\uFF0C\u4F46\u6FC0\u6D3B\u8FC7\u7A0B\u4E2D\u53D1\u751F\u672A\u77E5\u9519\u8BEF\u3002\u8BF7\u67E5\u770B dsh web \u65E5\u5FD7\u3002": "WeChat was authorized, but an unknown error occurred during activation. Check the dsh web logs.",
623
652
  "\u5FAE\u4FE1\u5DF2\u7ED1\u5B9A\uFF0C\u53EF\u4EE5\u5F00\u59CB\u5411\u5DF2\u7ED1\u5B9A\u7684\u673A\u5668\u4EBA\u53D1\u6D88\u606F\u3002": "WeChat is connected and ready for messages.",
624
653
  "\u8FD9\u4E2A\u5FAE\u4FE1\u8D26\u53F7\u5DF2\u7ECF\u7ED1\u5B9A\u5E76\u4FDD\u6301\u5728\u7EBF\u3002": "This WeChat account is connected and online.",
625
654
  "\u5FAE\u4FE1\u8D26\u53F7\u53CA\u672C\u673A\u51ED\u636E\u5DF2\u79FB\u9664\u3002": "The WeChat account and local credentials were removed.",
@@ -857,6 +886,16 @@ function translateDynamic(text5) {
857
886
  if (match) return `Remove \u201C${match[1]}\u201D from DeepSeek Harness?`;
858
887
  match = /^从 DeepSeek Harness 移除(.+)$/.exec(text5);
859
888
  if (match) return `Remove ${match[1]} from DeepSeek Harness`;
889
+ match = /^用于修复(.+)卡片按钮的一次性授权二维码$/.exec(text5);
890
+ if (match) return `One-time QR code for repairing card buttons for ${match[1]}`;
891
+ match = /^正在修复「(.+)」$/.exec(text5);
892
+ if (match) return `Repairing \u201C${match[1]}\u201D`;
893
+ match = /^修复(.+)的卡片按钮$/.exec(text5);
894
+ if (match) return `Repair card buttons for ${match[1]}`;
895
+ match = /^(.+)的修复二维码已生成,请使用飞书扫码。$/.exec(text5);
896
+ if (match) return `Repair QR code generated for ${match[1]}. Scan it with Feishu.`;
897
+ match = /^(.+)的卡片按钮已修复。$/.exec(text5);
898
+ if (match) return `Card buttons repaired for ${match[1]}.`;
860
899
  match = /^(检查连接|重试连接)(.+)$/.exec(text5);
861
900
  if (match) return `${localizeText(match[1])} ${match[2]}`;
862
901
  match = /^移除(.+)$/.exec(text5);
@@ -3031,6 +3070,7 @@ var FEISHU_RPC_CHANNEL = "/feishu";
3031
3070
  var FEISHU_ENDPOINTS = Object.freeze({
3032
3071
  status: "connection.status",
3033
3072
  beginProvisioning: "provision.begin",
3073
+ beginCallbackRepair: "bot.callback-repair.begin",
3034
3074
  pollProvisioning: "provision.poll",
3035
3075
  cancelProvisioning: "provision.cancel",
3036
3076
  bindCredentials: "bot.bind-credentials",
@@ -3042,6 +3082,10 @@ var FEISHU_ENDPOINTS = Object.freeze({
3042
3082
  testConnection: "connection.test",
3043
3083
  disconnect: "connection.disconnect"
3044
3084
  });
3085
+ var FEISHU_REGISTRATION_OPERATIONS = Object.freeze({
3086
+ PROVISION: "provision",
3087
+ CALLBACK_REPAIR: "callback_repair"
3088
+ });
3045
3089
  var CONNECTION_STATES = /* @__PURE__ */ new Set([
3046
3090
  "disconnected",
3047
3091
  "offline",
@@ -3076,6 +3120,9 @@ function optionalTimestamp(value) {
3076
3120
  function clamp2(value, min, max, fallback) {
3077
3121
  return typeof value === "number" && Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;
3078
3122
  }
3123
+ function normalizeRegistrationOperation(value) {
3124
+ return value === FEISHU_REGISTRATION_OPERATIONS.CALLBACK_REPAIR ? FEISHU_REGISTRATION_OPERATIONS.CALLBACK_REPAIR : FEISHU_REGISTRATION_OPERATIONS.PROVISION;
3125
+ }
3079
3126
  function unwrapRpcResult3(result) {
3080
3127
  if (!isRecord3(result) || typeof result.ok !== "boolean") {
3081
3128
  throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
@@ -3094,15 +3141,24 @@ function normalizeProvisioning2(value, now = Date.now()) {
3094
3141
  const attemptId = optionalString2(source.attemptId) ?? optionalString2(source.provisioningId);
3095
3142
  const verificationUrl = optionalString2(source.verificationUrl);
3096
3143
  const qrCodeDataUrl = optionalString2(source.qrCodeDataUrl);
3097
- if (!attemptId || !verificationUrl && !qrCodeDataUrl) {
3144
+ const submitted = source.submitted === true;
3145
+ if (!attemptId || !verificationUrl && !qrCodeDataUrl && !submitted) {
3098
3146
  throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u4E8C\u7EF4\u7801\u4FE1\u606F\u4E0D\u5B8C\u6574");
3099
3147
  }
3100
3148
  const explicitExpiry = optionalTimestamp(source.expiresAt);
3101
3149
  const expireIn = clamp2(source.expireIn, 1, 60 * 60, 5 * 60);
3150
+ const operation = normalizeRegistrationOperation(source.operation);
3151
+ const botId = optionalString2(source.botId);
3152
+ if (operation === FEISHU_REGISTRATION_OPERATIONS.CALLBACK_REPAIR && !botId) {
3153
+ throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u4FEE\u590D\u4FE1\u606F\u7F3A\u5C11 botId");
3154
+ }
3102
3155
  return {
3103
3156
  attemptId,
3157
+ operation,
3158
+ botId,
3104
3159
  verificationUrl,
3105
3160
  qrCodeDataUrl,
3161
+ submitted,
3106
3162
  expiresAt: explicitExpiry ?? now + expireIn * 1e3,
3107
3163
  pollIntervalMs: clamp2(source.pollIntervalMs, 800, 1e4, 1800)
3108
3164
  };
@@ -3220,6 +3276,7 @@ function normalizePollResult(value) {
3220
3276
  if (!status) throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u672A\u77E5\u7684\u521B\u5EFA\u72B6\u6001");
3221
3277
  const normalized = {
3222
3278
  status,
3279
+ operation: normalizeRegistrationOperation(value.operation),
3223
3280
  botId: optionalString2(value.botId),
3224
3281
  message: optionalString2(value.error?.message) ?? optionalString2(value.message),
3225
3282
  connection: void 0,
@@ -3738,6 +3795,8 @@ var CSS3 = String.raw`
3738
3795
  .bxf-healthSummary[data-error="true"] { color: var(--bxf-error); }
3739
3796
  .bxf-botActions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; justify-content: flex-end; }
3740
3797
  .bxf-botActions .bxf-button { flex: none; white-space: nowrap; }
3798
+ .bxf-botActions .bxf-repairButton { color: var(--bxf-accent); border-color: color-mix(in srgb, var(--bxf-accent) 35%, var(--dsw-alias-border-l2, #dee0e3)); }
3799
+ .bxf-botActions .bxf-repairButton:hover:not(:disabled) { background: color-mix(in srgb, var(--bxf-accent) 7%, transparent); }
3741
3800
 
3742
3801
  .bxf-confirm {
3743
3802
  border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3);
@@ -3829,6 +3888,10 @@ function installFeishuStyles() {
3829
3888
  }
3830
3889
 
3831
3890
  // plugin-src/client/channels/feishu/index.js
3891
+ var CALLBACK_REPAIR_OPERATION = FEISHU_REGISTRATION_OPERATIONS.CALLBACK_REPAIR;
3892
+ function isCallbackRepair(value) {
3893
+ return value?.operation === CALLBACK_REPAIR_OPERATION;
3894
+ }
3832
3895
  function SvgIcon({ children, size = 18, className, viewBox = "0 0 24 24" }) {
3833
3896
  return h2("svg", {
3834
3897
  width: size,
@@ -3988,7 +4051,12 @@ function safeVerificationHref(value) {
3988
4051
  if (!value) return void 0;
3989
4052
  try {
3990
4053
  const url = new URL(value);
3991
- return url.protocol === "https:" ? url.toString() : void 0;
4054
+ return url.protocol === "https:" && [
4055
+ "accounts.feishu.cn",
4056
+ "accounts.larksuite.com",
4057
+ "open.feishu.cn",
4058
+ "open.larksuite.com"
4059
+ ].includes(url.hostname) && !url.port && !url.username && !url.password ? url.toString() : void 0;
3992
4060
  } catch {
3993
4061
  return void 0;
3994
4062
  }
@@ -4004,6 +4072,8 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4004
4072
  const remaining = Math.max(0, provision.expiresAt - now);
4005
4073
  const expired = provision.expired === true || remaining === 0;
4006
4074
  const progress = Math.min(1, remaining / Math.max(1, provision.durationMs ?? remaining));
4075
+ const repairing = isCallbackRepair(provision);
4076
+ const botName = provision.botName ?? "\u6B64\u673A\u5668\u4EBA";
4007
4077
  React10.useEffect(() => setImageFailed(false), [qrSource]);
4008
4078
  return h2(
4009
4079
  "div",
@@ -4019,7 +4089,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4019
4089
  { className: "bxf-qrFrame dim-qrFrame" },
4020
4090
  qrSource && !imageFailed ? h2("img", {
4021
4091
  src: qrSource,
4022
- alt: "\u7528\u4E8E\u65B0\u589E DeepSeek Harness \u98DE\u4E66\u673A\u5668\u4EBA\u7684\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801",
4092
+ alt: repairing ? `\u7528\u4E8E\u4FEE\u590D${botName}\u5361\u7247\u6309\u94AE\u7684\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801` : "\u7528\u4E8E\u65B0\u589E DeepSeek Harness \u98DE\u4E66\u673A\u5668\u4EBA\u7684\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801",
4023
4093
  onError: () => setImageFailed(true)
4024
4094
  }) : h2(
4025
4095
  "div",
@@ -4058,16 +4128,16 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4058
4128
  "div",
4059
4129
  { className: "bxf-stateLabel dim-stateLabel" },
4060
4130
  h2("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }),
4061
- h2("span", null, "\u6B63\u5728\u6DFB\u52A0\u65B0\u673A\u5668\u4EBA")
4131
+ h2("span", null, repairing ? `\u6B63\u5728\u4FEE\u590D\u300C${botName}\u300D` : "\u6B63\u5728\u6DFB\u52A0\u65B0\u673A\u5668\u4EBA")
4062
4132
  ),
4063
- h2("h3", null, expired ? "\u5237\u65B0\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u521B\u5EFA\u673A\u5668\u4EBA"),
4064
- h2("p", null, "\u626B\u7801\u53EA\u4F1A\u65B0\u589E\u4E00\u4E2A\u673A\u5668\u4EBA\uFF0C\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA\u4F1A\u7EE7\u7EED\u6B63\u5E38\u6536\u53D1\u6D88\u606F\u3002"),
4133
+ h2("h3", null, expired ? "\u5237\u65B0\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : repairing ? "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u4FEE\u590D\u5361\u7247\u6309\u94AE" : "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u521B\u5EFA\u673A\u5668\u4EBA"),
4134
+ h2("p", null, repairing ? "\u626B\u7801\u4F1A\u66F4\u65B0\u73B0\u6709\u98DE\u4E66\u5E94\u7528\uFF0C\u53EA\u589E\u91CF\u8865\u5145\u5361\u7247\u6309\u94AE\u56DE\u8C03\uFF1B\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002\u786E\u8BA4\u540E\u6B64\u673A\u5668\u4EBA\u4F1A\u77ED\u6682\u91CD\u8FDE\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u53D7\u5F71\u54CD\u3002" : "\u626B\u7801\u53EA\u4F1A\u65B0\u589E\u4E00\u4E2A\u673A\u5668\u4EBA\uFF0C\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA\u4F1A\u7EE7\u7EED\u6B63\u5E38\u6536\u53D1\u6D88\u606F\u3002"),
4065
4135
  h2(
4066
4136
  "ol",
4067
4137
  { className: "bxf-steps dim-steps" },
4068
4138
  h2("li", null, "\u6253\u5F00\u98DE\u4E66\u79FB\u52A8\u7AEF\uFF0C\u4F7F\u7528\u626B\u4E00\u626B\u8BFB\u53D6\u4E8C\u7EF4\u7801"),
4069
- h2("li", null, "\u6838\u5BF9\u5E94\u7528\u540D\u79F0\u4E0E\u6743\u9650\u8303\u56F4\uFF0C\u5E76\u786E\u8BA4\u521B\u5EFA"),
4070
- h2("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u65B0\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u5C31\u7EEA")
4139
+ h2("li", null, repairing ? "\u6838\u5BF9\u73B0\u6709\u5E94\u7528\u540D\u79F0\uFF0C\u5E76\u786E\u8BA4\u53EA\u65B0\u589E\u5361\u7247\u56DE\u8C03" : "\u6838\u5BF9\u5E94\u7528\u540D\u79F0\u4E0E\u6743\u9650\u8303\u56F4\uFF0C\u5E76\u786E\u8BA4\u521B\u5EFA"),
4140
+ h2("li", null, repairing ? "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u5361\u7247\u6309\u94AE\u4FEE\u590D\u5B8C\u6210" : "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u65B0\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u5C31\u7EEA")
4071
4141
  ),
4072
4142
  h2(
4073
4143
  "div",
@@ -4084,14 +4154,15 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4084
4154
  rel: "noopener noreferrer"
4085
4155
  }, h2("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null,
4086
4156
  !expired ? h2(Button5, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null,
4087
- h2(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0")
4157
+ h2(Button5, { onClick: onCancel, disabled: busy }, repairing ? "\u53D6\u6D88\u4FEE\u590D" : "\u53D6\u6D88\u6DFB\u52A0")
4088
4158
  )
4089
4159
  )
4090
4160
  )
4091
4161
  );
4092
4162
  }
4093
- function ProvisionProgress({ phase, onCancel, busy }) {
4163
+ function ProvisionProgress({ phase, provision, onCancel, busy }) {
4094
4164
  const connecting = phase === "connecting";
4165
+ const repairing = isCallbackRepair(provision);
4095
4166
  return h2(
4096
4167
  "div",
4097
4168
  {
@@ -4099,16 +4170,17 @@ function ProvisionProgress({ phase, onCancel, busy }) {
4099
4170
  "aria-busy": "true"
4100
4171
  },
4101
4172
  h2("div", { className: "dim-spinner", "aria-hidden": "true" }),
4102
- h2("h3", null, connecting ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA" : "\u6B63\u5728\u51C6\u5907\u6388\u6743\u4E8C\u7EF4\u7801"),
4103
- h2("p", null, connecting ? "\u6B63\u5728\u5B89\u5168\u4FDD\u5B58\u51ED\u636E\u5E76\u68C0\u67E5\u65B0\u673A\u5668\u4EBA\u7684\u6D88\u606F\u901A\u9053\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002" : "\u6B63\u5728\u5411\u98DE\u4E66\u7533\u8BF7\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002"),
4104
- connecting ? h2(
4173
+ h2("h3", null, connecting ? repairing ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u5B8C\u6210\u5361\u7247\u6309\u94AE\u4FEE\u590D" : "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA" : repairing ? "\u6B63\u5728\u51C6\u5907\u4FEE\u590D\u4E8C\u7EF4\u7801" : "\u6B63\u5728\u51C6\u5907\u6388\u6743\u4E8C\u7EF4\u7801"),
4174
+ h2("p", null, connecting ? repairing ? "\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u9A8C\u8BC1\u5361\u7247\u6309\u94AE\u56DE\u8C03\u5E76\u91CD\u8FDE\u6B64\u673A\u5668\u4EBA\uFF1B\u6B64\u9636\u6BB5\u65E0\u6CD5\u53D6\u6D88\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002" : "\u6B63\u5728\u5B89\u5168\u4FDD\u5B58\u51ED\u636E\u5E76\u68C0\u67E5\u65B0\u673A\u5668\u4EBA\u7684\u6D88\u606F\u901A\u9053\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002" : repairing ? "\u6B63\u5728\u4E3A\u73B0\u6709\u98DE\u4E66\u5E94\u7528\u7533\u8BF7\u4E00\u6B21\u6027\u66F4\u65B0\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002" : "\u6B63\u5728\u5411\u98DE\u4E66\u7533\u8BF7\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002"),
4175
+ connecting && onCancel ? h2(
4105
4176
  "div",
4106
4177
  { className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } },
4107
- h2(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0")
4178
+ h2(Button5, { onClick: onCancel, disabled: busy }, repairing ? "\u53D6\u6D88\u4FEE\u590D" : "\u53D6\u6D88\u6DFB\u52A0")
4108
4179
  ) : null
4109
4180
  );
4110
4181
  }
4111
- function ProvisionError2({ error, onRetry, onCancel, busy }) {
4182
+ function ProvisionError2({ error, provision, onRetry, onCancel, busy }) {
4183
+ const repairing = isCallbackRepair(provision);
4112
4184
  return h2(
4113
4185
  "div",
4114
4186
  { className: "bxf-card bxf-provisionCard dim-surfaceCard" },
@@ -4118,7 +4190,7 @@ function ProvisionError2({ error, onRetry, onCancel, busy }) {
4118
4190
  h2(
4119
4191
  "div",
4120
4192
  null,
4121
- h2("h3", null, "\u65B0\u673A\u5668\u4EBA\u6CA1\u6709\u6DFB\u52A0\u5B8C\u6210"),
4193
+ h2("h3", null, repairing ? "\u5361\u7247\u6309\u94AE\u6CA1\u6709\u4FEE\u590D\u5B8C\u6210" : "\u65B0\u673A\u5668\u4EBA\u6CA1\u6709\u6DFB\u52A0\u5B8C\u6210"),
4122
4194
  h2("p", null, error.message),
4123
4195
  error.code ? h2("span", { className: "bxf-errorCode" }, error.code) : null,
4124
4196
  h2(
@@ -4203,10 +4275,12 @@ function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) {
4203
4275
  function BotCard({
4204
4276
  connection,
4205
4277
  busy,
4278
+ repairDisabled,
4206
4279
  actionError,
4207
4280
  testNotice,
4208
4281
  removing,
4209
4282
  onReconnect,
4283
+ onRepairCallback,
4210
4284
  onWorkspaceSave,
4211
4285
  onRequestRemove,
4212
4286
  onConfirmRemove,
@@ -4299,6 +4373,13 @@ function BotCard({
4299
4373
  "aria-busy": busy === "reconnect" ? "true" : void 0,
4300
4374
  "aria-label": `${connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"}${bot.name}`
4301
4375
  }, busy === "reconnect" ? connected ? "\u68C0\u67E5\u4E2D\u2026" : "\u6B63\u5728\u8FDE\u63A5\u2026" : connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
4376
+ h2(Button5, {
4377
+ className: "bxf-repairButton dim-cardAction",
4378
+ onClick: onRepairCallback,
4379
+ disabled: Boolean(busy) || repairDisabled,
4380
+ "aria-busy": busy === "callback-repair" ? "true" : void 0,
4381
+ "aria-label": `\u4FEE\u590D${bot.name}\u7684\u5361\u7247\u6309\u94AE`
4382
+ }, busy === "callback-repair" ? "\u7B49\u5F85\u626B\u7801\u2026" : "\u4FEE\u590D\u5361\u7247\u6309\u94AE"),
4302
4383
  h2(Button5, {
4303
4384
  className: "dim-cardAction",
4304
4385
  kind: "danger",
@@ -4335,11 +4416,13 @@ function BotList(props) {
4335
4416
  { key: bot.botId },
4336
4417
  h2(BotCard, {
4337
4418
  connection: bot,
4338
- busy: props.busyByBot[bot.botId],
4419
+ busy: props.busyByBot[bot.botId] ?? (isCallbackRepair(props.provisioning) && props.provisioning.botId === bot.botId ? "callback-repair" : void 0),
4420
+ repairDisabled: Boolean(props.provisioning),
4339
4421
  actionError: props.errorsByBot[bot.botId],
4340
4422
  testNotice: props.testNoticesByBot[bot.botId],
4341
4423
  removing: props.removeTargetId === bot.botId,
4342
4424
  onReconnect: () => props.onReconnect(bot),
4425
+ onRepairCallback: () => props.onRepairCallback(bot),
4343
4426
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
4344
4427
  onRequestRemove: () => props.onRequestRemove(bot),
4345
4428
  onConfirmRemove: () => props.onConfirmRemove(bot),
@@ -4382,11 +4465,12 @@ function mergeFeishuSnapshotState(current, snapshot, { restoreProvisioning = fal
4382
4465
  if (snapshot.revision > 0 && current.revision > snapshot.revision) return current;
4383
4466
  let provisioning = current.provisioning;
4384
4467
  if (!provisioning && restoreProvisioning && snapshot.provisioning) {
4468
+ const submitted = snapshot.provisioning.submitted === true;
4385
4469
  provisioning = {
4386
- phase: snapshot.state === "connecting" ? "connecting" : "qr",
4470
+ phase: submitted || snapshot.state === "connecting" ? "connecting" : "qr",
4387
4471
  ...snapshot.provisioning,
4388
4472
  durationMs: Math.max(1, snapshot.provisioning.expiresAt - now),
4389
- expired: snapshot.provisioning.expiresAt <= now
4473
+ expired: !submitted && snapshot.provisioning.expiresAt <= now
4390
4474
  };
4391
4475
  }
4392
4476
  return {
@@ -4501,7 +4585,15 @@ function FeishuSettingsTab({ rpcCall }) {
4501
4585
  node.focus({ preventScroll: true });
4502
4586
  setFocusBotId(null);
4503
4587
  }, [focusBotId, model.bots]);
4504
- const startProvisioning = React10.useCallback(async ({ replace = false } = {}) => {
4588
+ const startProvisioning = React10.useCallback(async ({
4589
+ replace = false,
4590
+ operation = FEISHU_REGISTRATION_OPERATIONS.PROVISION,
4591
+ bot
4592
+ } = {}) => {
4593
+ const repairing = operation === CALLBACK_REPAIR_OPERATION;
4594
+ const botId = repairing ? bot?.botId ?? model.provisioning?.botId : void 0;
4595
+ const botName = repairing ? bot?.bot?.name ?? model.provisioning?.botName : void 0;
4596
+ if (repairing && !botId) return;
4505
4597
  setCredentialOpen(false);
4506
4598
  setCredentialError(null);
4507
4599
  setProvisionBusy(true);
@@ -4510,16 +4602,27 @@ function FeishuSettingsTab({ rpcCall }) {
4510
4602
  setModel((current) => ({
4511
4603
  ...current,
4512
4604
  phase: current.phase === "loading" ? "ready" : current.phase,
4513
- provisioning: { phase: "creating" }
4605
+ provisioning: {
4606
+ phase: "creating",
4607
+ operation,
4608
+ ...botId ? { botId } : {},
4609
+ ...botName ? { botName } : {}
4610
+ }
4514
4611
  }));
4515
4612
  try {
4516
4613
  if (replace && previousAttemptId) {
4517
- await invoke(FEISHU_ENDPOINTS.cancelProvisioning, { attemptId: previousAttemptId });
4614
+ try {
4615
+ await invoke(FEISHU_ENDPOINTS.cancelProvisioning, { attemptId: previousAttemptId });
4616
+ } catch {
4617
+ }
4518
4618
  }
4519
4619
  const provision2 = normalizeProvisioning2(await invoke(
4520
- FEISHU_ENDPOINTS.beginProvisioning,
4521
- { locale: "zh-CN" }
4620
+ repairing ? FEISHU_ENDPOINTS.beginCallbackRepair : FEISHU_ENDPOINTS.beginProvisioning,
4621
+ repairing ? { botId } : { locale: "zh-CN" }
4522
4622
  ));
4623
+ if (repairing && (provision2.operation !== CALLBACK_REPAIR_OPERATION || provision2.botId !== botId)) {
4624
+ throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u5361\u7247\u4FEE\u590D\u4E8C\u7EF4\u7801");
4625
+ }
4523
4626
  const timestamp7 = Date.now();
4524
4627
  setNow(timestamp7);
4525
4628
  setModel((current) => ({
@@ -4527,20 +4630,34 @@ function FeishuSettingsTab({ rpcCall }) {
4527
4630
  provisioning: {
4528
4631
  phase: "qr",
4529
4632
  ...provision2,
4633
+ ...botName ? { botName } : {},
4530
4634
  durationMs: Math.max(1, provision2.expiresAt - timestamp7),
4531
4635
  expired: false
4532
4636
  }
4533
4637
  }));
4534
- announce("\u6388\u6743\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002");
4638
+ announce(repairing ? `${botName ?? "\u673A\u5668\u4EBA"}\u7684\u4FEE\u590D\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002` : "\u6388\u6743\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002");
4535
4639
  } catch (error) {
4536
4640
  setModel((current) => ({
4537
4641
  ...current,
4538
- provisioning: { phase: "error", error: presentError3(error) }
4642
+ provisioning: {
4643
+ phase: "error",
4644
+ operation,
4645
+ ...botId ? { botId } : {},
4646
+ ...botName ? { botName } : {},
4647
+ ...replace && previousAttemptId ? { attemptId: previousAttemptId } : {},
4648
+ error: presentError3(error)
4649
+ }
4539
4650
  }));
4540
4651
  } finally {
4541
4652
  setProvisionBusy(false);
4542
4653
  }
4543
- }, [announce, invoke, model.provisioning?.attemptId]);
4654
+ }, [
4655
+ announce,
4656
+ invoke,
4657
+ model.provisioning?.attemptId,
4658
+ model.provisioning?.botId,
4659
+ model.provisioning?.botName
4660
+ ]);
4544
4661
  const bindCredentials = React10.useCallback(async ({ identity, secret }) => {
4545
4662
  const snapshotVersion = workspaceFence.beginMutation();
4546
4663
  setCredentialBusy(true);
@@ -4564,23 +4681,64 @@ function FeishuSettingsTab({ rpcCall }) {
4564
4681
  }
4565
4682
  }, [announce, invoke, loadStatus, mergeSnapshot, workspaceFence]);
4566
4683
  const cancelProvisioning = React10.useCallback(async () => {
4567
- const attemptId = model.provisioning?.attemptId;
4684
+ const activeProvision = model.provisioning;
4685
+ const attemptId = activeProvision?.attemptId;
4686
+ const repairing = isCallbackRepair(activeProvision);
4687
+ const targetBot = repairing ? model.bots.find((bot) => bot.botId === activeProvision?.botId) : void 0;
4568
4688
  setProvisionBusy(true);
4569
4689
  try {
4570
- if (attemptId) await invoke(FEISHU_ENDPOINTS.cancelProvisioning, { attemptId });
4690
+ const result = attemptId ? normalizePollResult(await invoke(FEISHU_ENDPOINTS.cancelProvisioning, { attemptId })) : null;
4691
+ if (repairing && result) {
4692
+ if (result.operation !== CALLBACK_REPAIR_OPERATION || result.botId !== activeProvision.botId) {
4693
+ throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u6CE8\u518C\u8FDB\u5EA6");
4694
+ }
4695
+ if (result.status === "connecting") {
4696
+ setModel((current) => current.provisioning?.attemptId === attemptId ? {
4697
+ ...current,
4698
+ provisioning: {
4699
+ ...current.provisioning,
4700
+ ...result.provisioning ?? {},
4701
+ phase: "connecting",
4702
+ submitted: true,
4703
+ expired: false
4704
+ }
4705
+ } : current);
4706
+ announce("\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u9A8C\u8BC1\u5361\u7247\u6309\u94AE\u56DE\u8C03\u5E76\u91CD\u8FDE\u6B64\u673A\u5668\u4EBA\uFF1B\u6B64\u9636\u6BB5\u65E0\u6CD5\u53D6\u6D88\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002");
4707
+ return;
4708
+ }
4709
+ if (result.status === "connected") {
4710
+ const targetBotName = targetBot?.bot.name ?? activeProvision.botName ?? "\u673A\u5668\u4EBA";
4711
+ setModel((current) => ({ ...current, provisioning: null }));
4712
+ announce(`${targetBotName}\u7684\u5361\u7247\u6309\u94AE\u5DF2\u4FEE\u590D\u3002`);
4713
+ if (activeProvision.botId) setFocusBotId(activeProvision.botId);
4714
+ await loadStatus({ silent: true, restoreProvisioning: false });
4715
+ return;
4716
+ }
4717
+ }
4571
4718
  setModel((current) => ({ ...current, provisioning: null }));
4572
- announce("\u5DF2\u53D6\u6D88\u6DFB\u52A0\u673A\u5668\u4EBA\u3002");
4719
+ announce(repairing ? "\u5DF2\u53D6\u6D88\u5361\u7247\u6309\u94AE\u4FEE\u590D\u3002" : "\u5DF2\u53D6\u6D88\u6DFB\u52A0\u673A\u5668\u4EBA\u3002");
4573
4720
  await loadStatus({ silent: true, restoreProvisioning: false });
4574
- scheduleAnimationFrame(() => addButtonRef.current?.focus(), "focus");
4721
+ scheduleAnimationFrame(() => {
4722
+ if (repairing && activeProvision.botId) {
4723
+ cardRefs.current.get(activeProvision.botId)?.focus();
4724
+ } else {
4725
+ addButtonRef.current?.focus();
4726
+ }
4727
+ }, "focus");
4575
4728
  } catch (error) {
4576
4729
  setModel((current) => ({
4577
4730
  ...current,
4578
- provisioning: { phase: "error", attemptId, error: presentError3(error) }
4731
+ provisioning: {
4732
+ ...activeProvision,
4733
+ phase: "error",
4734
+ attemptId,
4735
+ error: presentError3(error)
4736
+ }
4579
4737
  }));
4580
4738
  } finally {
4581
4739
  setProvisionBusy(false);
4582
4740
  }
4583
- }, [announce, invoke, loadStatus, model.provisioning?.attemptId, scheduleAnimationFrame]);
4741
+ }, [announce, invoke, loadStatus, model.bots, model.provisioning, scheduleAnimationFrame]);
4584
4742
  const countdownAttemptId = model.provisioning?.attemptId;
4585
4743
  const countdownPhase = model.provisioning?.phase;
4586
4744
  const countdownExpiresAt = model.provisioning?.expiresAt;
@@ -4609,23 +4767,26 @@ function FeishuSettingsTab({ rpcCall }) {
4609
4767
  { attemptId: provision2.attemptId },
4610
4768
  controller.signal
4611
4769
  ));
4770
+ if (result.operation !== provision2.operation || isCallbackRepair(provision2) && result.botId !== provision2.botId) {
4771
+ throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u6CE8\u518C\u8FDB\u5EA6");
4772
+ }
4612
4773
  if (result.status === "connected") {
4613
4774
  const snapshot = await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false });
4614
- const newBot = snapshot?.bots.find((bot) => bot.botId === result.botId);
4775
+ const targetBot = snapshot?.bots.find((bot) => bot.botId === result.botId);
4615
4776
  if (!snapshot) {
4616
- throw new Error("\u673A\u5668\u4EBA\u5DF2\u7ECF\u521B\u5EFA\uFF0C\u4F46\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u8FDE\u63A5\u72B6\u6001");
4777
+ throw new Error(isCallbackRepair(provision2) ? "\u5361\u7247\u6309\u94AE\u5DF2\u66F4\u65B0\uFF0C\u4F46\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u673A\u5668\u4EBA\u8FDE\u63A5\u72B6\u6001" : "\u673A\u5668\u4EBA\u5DF2\u7ECF\u521B\u5EFA\uFF0C\u4F46\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u8FDE\u63A5\u72B6\u6001");
4617
4778
  }
4618
- if (!newBot?.connected) {
4779
+ if (!targetBot?.connected) {
4619
4780
  setModel((current) => current.provisioning?.attemptId === provision2.attemptId ? { ...current, provisioning: { ...current.provisioning, phase: "connecting" } } : current);
4620
4781
  return;
4621
4782
  }
4622
4783
  setModel((current) => ({ ...current, provisioning: null }));
4623
- announce(newBot ? `${newBot.bot.name}\u5DF2\u8FDE\u63A5\uFF0C\u53EF\u4EE5\u5728\u98DE\u4E66\u4E2D\u5F00\u59CB\u804A\u5929\u3002` : "\u65B0\u98DE\u4E66\u673A\u5668\u4EBA\u5DF2\u8FDE\u63A5\uFF0C\u53EF\u4EE5\u5F00\u59CB\u804A\u5929\u3002");
4784
+ announce(isCallbackRepair(provision2) ? `${targetBot.bot.name}\u7684\u5361\u7247\u6309\u94AE\u5DF2\u4FEE\u590D\u3002` : targetBot ? `${targetBot.bot.name}\u5DF2\u8FDE\u63A5\uFF0C\u53EF\u4EE5\u5728\u98DE\u4E66\u4E2D\u5F00\u59CB\u804A\u5929\u3002` : "\u65B0\u98DE\u4E66\u673A\u5668\u4EBA\u5DF2\u8FDE\u63A5\uFF0C\u53EF\u4EE5\u5F00\u59CB\u804A\u5929\u3002");
4624
4785
  if (result.botId) setFocusBotId(result.botId);
4625
4786
  return;
4626
4787
  }
4627
4788
  if (result.status === "failed") {
4628
- const error = new Error(result.message ?? "\u98DE\u4E66\u5E94\u7528\u521B\u5EFA\u5931\u8D25");
4789
+ const error = new Error(result.message ?? (isCallbackRepair(provision2) ? "\u98DE\u4E66\u5361\u7247\u6309\u94AE\u4FEE\u590D\u5931\u8D25" : "\u98DE\u4E66\u5E94\u7528\u521B\u5EFA\u5931\u8D25"));
4629
4790
  error.code = "FEISHU_PROVISION_FAILED";
4630
4791
  throw error;
4631
4792
  }
@@ -4650,6 +4811,7 @@ function FeishuSettingsTab({ rpcCall }) {
4650
4811
  setModel((current) => current.provisioning?.attemptId === provision2.attemptId ? {
4651
4812
  ...current,
4652
4813
  provisioning: {
4814
+ ...current.provisioning,
4653
4815
  phase: "error",
4654
4816
  attemptId: provision2.attemptId,
4655
4817
  error: presentError3(error)
@@ -4678,6 +4840,20 @@ function FeishuSettingsTab({ rpcCall }) {
4678
4840
  return next;
4679
4841
  });
4680
4842
  }, []);
4843
+ const repairCallback = React10.useCallback((connection) => {
4844
+ if (model.provisioning) return;
4845
+ setRemoveTargetId(null);
4846
+ setBotError(connection.botId, null);
4847
+ setTestNoticesByBot((current) => {
4848
+ const next = { ...current };
4849
+ delete next[connection.botId];
4850
+ return next;
4851
+ });
4852
+ void startProvisioning({
4853
+ operation: CALLBACK_REPAIR_OPERATION,
4854
+ bot: connection
4855
+ });
4856
+ }, [model.provisioning, setBotError, startProvisioning]);
4681
4857
  const reconnectOneBot = React10.useCallback(async (connection) => {
4682
4858
  const { botId, bot } = connection;
4683
4859
  const snapshotVersion = workspaceFence.beginMutation();
@@ -4771,28 +4947,48 @@ function FeishuSettingsTab({ rpcCall }) {
4771
4947
  }
4772
4948
  }, [announce, invoke, loadStatus, mergeSnapshot, scheduleAnimationFrame, setBotBusy, setBotError, workspaceFence]);
4773
4949
  const provision = model.provisioning;
4950
+ const provisionBot = provision?.botId ? model.bots.find((bot) => bot.botId === provision.botId) ?? { botId: provision.botId, bot: { name: provision.botName ?? "\u6B64\u673A\u5668\u4EBA" } } : void 0;
4951
+ const restartProvisioning = ({ replace = false } = {}) => startProvisioning({
4952
+ replace,
4953
+ operation: provision?.operation ?? FEISHU_REGISTRATION_OPERATIONS.PROVISION,
4954
+ bot: provisionBot
4955
+ });
4774
4956
  let provisionContent = null;
4775
4957
  if (provision?.phase === "creating") {
4776
- provisionContent = h2(ProvisionProgress, { phase: "creating", busy: provisionBusy });
4958
+ provisionContent = h2(ProvisionProgress, {
4959
+ phase: "creating",
4960
+ provision,
4961
+ busy: provisionBusy
4962
+ });
4777
4963
  } else if (provision?.phase === "qr") {
4778
4964
  provisionContent = h2(QrPane, {
4779
4965
  provision,
4780
4966
  now,
4781
- onRefresh: () => void startProvisioning({ replace: true }),
4967
+ onRefresh: () => void restartProvisioning({ replace: true }),
4782
4968
  onCancel: () => void cancelProvisioning(),
4783
4969
  busy: provisionBusy || model.phase !== "ready"
4784
4970
  });
4785
4971
  } else if (provision?.phase === "connecting") {
4786
4972
  provisionContent = h2(ProvisionProgress, {
4787
4973
  phase: "connecting",
4788
- onCancel: () => void cancelProvisioning(),
4974
+ provision,
4975
+ onCancel: isCallbackRepair(provision) ? void 0 : () => void cancelProvisioning(),
4789
4976
  busy: provisionBusy
4790
4977
  });
4791
4978
  } else if (provision?.phase === "error") {
4792
4979
  provisionContent = h2(ProvisionError2, {
4793
4980
  error: provision.error,
4794
- onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }),
4795
- onCancel: () => void cancelProvisioning(),
4981
+ provision,
4982
+ onRetry: () => void restartProvisioning({ replace: Boolean(provision.attemptId) }),
4983
+ onCancel: () => {
4984
+ const targetBotId = provision.botId;
4985
+ setModel((current) => ({ ...current, provisioning: null }));
4986
+ void loadStatus({ silent: true, restoreProvisioning: false });
4987
+ scheduleAnimationFrame(() => {
4988
+ if (targetBotId) cardRefs.current.get(targetBotId)?.focus();
4989
+ else addButtonRef.current?.focus();
4990
+ }, "focus");
4991
+ },
4796
4992
  busy: provisionBusy
4797
4993
  });
4798
4994
  }
@@ -4862,7 +5058,9 @@ function FeishuSettingsTab({ rpcCall }) {
4862
5058
  errorsByBot,
4863
5059
  testNoticesByBot,
4864
5060
  removeTargetId,
5061
+ provisioning: provision,
4865
5062
  onReconnect: (bot) => void reconnectOneBot(bot),
5063
+ onRepairCallback: repairCallback,
4866
5064
  onWorkspaceSave: saveWorkspace,
4867
5065
  onRequestRemove: requestRemove,
4868
5066
  onConfirmRemove: (bot) => void confirmRemove(bot),
@@ -8901,8 +9099,9 @@ var CSS11 = String.raw`
8901
9099
  .dim-logoSlack svg { width: 21px; height: 21px; }
8902
9100
  .dim-logoWhatsapp { color: white; background: #25d366; }
8903
9101
  .dim-logoWhatsapp svg { width: 21px; height: 21px; }
8904
- .dim-channelCopy { min-width: 0; display: block; }
9102
+ .dim-channelCopy { min-width: 0; display: grid; }
8905
9103
  .dim-channelCopy strong { overflow: hidden; color: inherit; font-size: 14px; line-height: 20px; font-weight: 680; text-overflow: ellipsis; white-space: nowrap; }
9104
+ .dim-channelNote { overflow: hidden; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 10px; line-height: 13px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
8906
9105
  .dim-divider { width: 1px; min-height: 520px; background: var(--dsw-alias-border-l1, #eef0f3); }
8907
9106
  .dim-panel { min-width: 0; container-type: inline-size; }
8908
9107
  .dim-panel .bxf-page, .dim-panel .dxw-page, .dim-panel .ddt-page, .dim-panel .dqq-page, .dim-panel .dwecom-page, .dim-panel .dsl-page, .dim-panel .dwa-page { width: 100%; max-width: none; padding: 0 0 24px; }
@@ -9131,7 +9330,7 @@ var CHANNELS = Object.freeze([
9131
9330
  { id: "telegram", label: "Telegram" },
9132
9331
  { id: "discord", label: "Discord" },
9133
9332
  { id: "whatsapp", label: "WhatsApp" },
9134
- { id: "office", label: "AI Office" }
9333
+ { id: "office", label: "AI Office", note: "\uFF08\u5B9E\u9A8C\u529F\u80FD\uFF09" }
9135
9334
  ]);
9136
9335
  function WeixinLogo() {
9137
9336
  return h2(
@@ -9283,7 +9482,8 @@ function IMSettingsTab({
9283
9482
  h2(
9284
9483
  "span",
9285
9484
  { className: "dim-channelCopy" },
9286
- h2("strong", null, channel4.label)
9485
+ h2("strong", null, channel4.label),
9486
+ channel4.note ? h2("small", { className: "dim-channelNote" }, channel4.note) : null
9287
9487
  )
9288
9488
  ))
9289
9489
  ),