@xmanrui/dsh-im 0.15.0 → 0.17.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
@@ -587,6 +587,27 @@ var EN = Object.freeze({
587
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",
588
588
  "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00": "Open in Feishu",
589
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",
590
611
  "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA": "Confirmed. Connecting the new bot",
591
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.",
592
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",
@@ -865,6 +886,16 @@ function translateDynamic(text5) {
865
886
  if (match) return `Remove \u201C${match[1]}\u201D from DeepSeek Harness?`;
866
887
  match = /^从 DeepSeek Harness 移除(.+)$/.exec(text5);
867
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]}.`;
868
899
  match = /^(检查连接|重试连接)(.+)$/.exec(text5);
869
900
  if (match) return `${localizeText(match[1])} ${match[2]}`;
870
901
  match = /^移除(.+)$/.exec(text5);
@@ -3039,6 +3070,7 @@ var FEISHU_RPC_CHANNEL = "/feishu";
3039
3070
  var FEISHU_ENDPOINTS = Object.freeze({
3040
3071
  status: "connection.status",
3041
3072
  beginProvisioning: "provision.begin",
3073
+ beginCallbackRepair: "bot.callback-repair.begin",
3042
3074
  pollProvisioning: "provision.poll",
3043
3075
  cancelProvisioning: "provision.cancel",
3044
3076
  bindCredentials: "bot.bind-credentials",
@@ -3050,6 +3082,10 @@ var FEISHU_ENDPOINTS = Object.freeze({
3050
3082
  testConnection: "connection.test",
3051
3083
  disconnect: "connection.disconnect"
3052
3084
  });
3085
+ var FEISHU_REGISTRATION_OPERATIONS = Object.freeze({
3086
+ PROVISION: "provision",
3087
+ CALLBACK_REPAIR: "callback_repair"
3088
+ });
3053
3089
  var CONNECTION_STATES = /* @__PURE__ */ new Set([
3054
3090
  "disconnected",
3055
3091
  "offline",
@@ -3084,6 +3120,9 @@ function optionalTimestamp(value) {
3084
3120
  function clamp2(value, min, max, fallback) {
3085
3121
  return typeof value === "number" && Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;
3086
3122
  }
3123
+ function normalizeRegistrationOperation(value) {
3124
+ return value === FEISHU_REGISTRATION_OPERATIONS.CALLBACK_REPAIR ? FEISHU_REGISTRATION_OPERATIONS.CALLBACK_REPAIR : FEISHU_REGISTRATION_OPERATIONS.PROVISION;
3125
+ }
3087
3126
  function unwrapRpcResult3(result) {
3088
3127
  if (!isRecord3(result) || typeof result.ok !== "boolean") {
3089
3128
  throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BC6\u522B\u7684\u54CD\u5E94");
@@ -3102,15 +3141,24 @@ function normalizeProvisioning2(value, now = Date.now()) {
3102
3141
  const attemptId = optionalString2(source.attemptId) ?? optionalString2(source.provisioningId);
3103
3142
  const verificationUrl = optionalString2(source.verificationUrl);
3104
3143
  const qrCodeDataUrl = optionalString2(source.qrCodeDataUrl);
3105
- if (!attemptId || !verificationUrl && !qrCodeDataUrl) {
3144
+ const submitted = source.submitted === true;
3145
+ if (!attemptId || !verificationUrl && !qrCodeDataUrl && !submitted) {
3106
3146
  throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u4E8C\u7EF4\u7801\u4FE1\u606F\u4E0D\u5B8C\u6574");
3107
3147
  }
3108
3148
  const explicitExpiry = optionalTimestamp(source.expiresAt);
3109
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
+ }
3110
3155
  return {
3111
3156
  attemptId,
3157
+ operation,
3158
+ botId,
3112
3159
  verificationUrl,
3113
3160
  qrCodeDataUrl,
3161
+ submitted,
3114
3162
  expiresAt: explicitExpiry ?? now + expireIn * 1e3,
3115
3163
  pollIntervalMs: clamp2(source.pollIntervalMs, 800, 1e4, 1800)
3116
3164
  };
@@ -3228,6 +3276,7 @@ function normalizePollResult(value) {
3228
3276
  if (!status) throw new Error("\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u672A\u77E5\u7684\u521B\u5EFA\u72B6\u6001");
3229
3277
  const normalized = {
3230
3278
  status,
3279
+ operation: normalizeRegistrationOperation(value.operation),
3231
3280
  botId: optionalString2(value.botId),
3232
3281
  message: optionalString2(value.error?.message) ?? optionalString2(value.message),
3233
3282
  connection: void 0,
@@ -3746,6 +3795,8 @@ var CSS3 = String.raw`
3746
3795
  .bxf-healthSummary[data-error="true"] { color: var(--bxf-error); }
3747
3796
  .bxf-botActions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; justify-content: flex-end; }
3748
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); }
3749
3800
 
3750
3801
  .bxf-confirm {
3751
3802
  border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3);
@@ -3837,6 +3888,10 @@ function installFeishuStyles() {
3837
3888
  }
3838
3889
 
3839
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
+ }
3840
3895
  function SvgIcon({ children, size = 18, className, viewBox = "0 0 24 24" }) {
3841
3896
  return h2("svg", {
3842
3897
  width: size,
@@ -3996,7 +4051,12 @@ function safeVerificationHref(value) {
3996
4051
  if (!value) return void 0;
3997
4052
  try {
3998
4053
  const url = new URL(value);
3999
- 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;
4000
4060
  } catch {
4001
4061
  return void 0;
4002
4062
  }
@@ -4012,6 +4072,8 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4012
4072
  const remaining = Math.max(0, provision.expiresAt - now);
4013
4073
  const expired = provision.expired === true || remaining === 0;
4014
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";
4015
4077
  React10.useEffect(() => setImageFailed(false), [qrSource]);
4016
4078
  return h2(
4017
4079
  "div",
@@ -4027,7 +4089,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4027
4089
  { className: "bxf-qrFrame dim-qrFrame" },
4028
4090
  qrSource && !imageFailed ? h2("img", {
4029
4091
  src: qrSource,
4030
- 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",
4031
4093
  onError: () => setImageFailed(true)
4032
4094
  }) : h2(
4033
4095
  "div",
@@ -4066,16 +4128,16 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4066
4128
  "div",
4067
4129
  { className: "bxf-stateLabel dim-stateLabel" },
4068
4130
  h2("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }),
4069
- 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")
4070
4132
  ),
4071
- h2("h3", null, expired ? "\u5237\u65B0\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u521B\u5EFA\u673A\u5668\u4EBA"),
4072
- 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"),
4073
4135
  h2(
4074
4136
  "ol",
4075
4137
  { className: "bxf-steps dim-steps" },
4076
4138
  h2("li", null, "\u6253\u5F00\u98DE\u4E66\u79FB\u52A8\u7AEF\uFF0C\u4F7F\u7528\u626B\u4E00\u626B\u8BFB\u53D6\u4E8C\u7EF4\u7801"),
4077
- h2("li", null, "\u6838\u5BF9\u5E94\u7528\u540D\u79F0\u4E0E\u6743\u9650\u8303\u56F4\uFF0C\u5E76\u786E\u8BA4\u521B\u5EFA"),
4078
- 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")
4079
4141
  ),
4080
4142
  h2(
4081
4143
  "div",
@@ -4092,14 +4154,15 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
4092
4154
  rel: "noopener noreferrer"
4093
4155
  }, h2("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null,
4094
4156
  !expired ? h2(Button5, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null,
4095
- 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")
4096
4158
  )
4097
4159
  )
4098
4160
  )
4099
4161
  );
4100
4162
  }
4101
- function ProvisionProgress({ phase, onCancel, busy }) {
4163
+ function ProvisionProgress({ phase, provision, onCancel, busy }) {
4102
4164
  const connecting = phase === "connecting";
4165
+ const repairing = isCallbackRepair(provision);
4103
4166
  return h2(
4104
4167
  "div",
4105
4168
  {
@@ -4107,16 +4170,17 @@ function ProvisionProgress({ phase, onCancel, busy }) {
4107
4170
  "aria-busy": "true"
4108
4171
  },
4109
4172
  h2("div", { className: "dim-spinner", "aria-hidden": "true" }),
4110
- 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"),
4111
- 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"),
4112
- 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(
4113
4176
  "div",
4114
4177
  { className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } },
4115
- 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")
4116
4179
  ) : null
4117
4180
  );
4118
4181
  }
4119
- function ProvisionError2({ error, onRetry, onCancel, busy }) {
4182
+ function ProvisionError2({ error, provision, onRetry, onCancel, busy }) {
4183
+ const repairing = isCallbackRepair(provision);
4120
4184
  return h2(
4121
4185
  "div",
4122
4186
  { className: "bxf-card bxf-provisionCard dim-surfaceCard" },
@@ -4126,7 +4190,7 @@ function ProvisionError2({ error, onRetry, onCancel, busy }) {
4126
4190
  h2(
4127
4191
  "div",
4128
4192
  null,
4129
- 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"),
4130
4194
  h2("p", null, error.message),
4131
4195
  error.code ? h2("span", { className: "bxf-errorCode" }, error.code) : null,
4132
4196
  h2(
@@ -4211,10 +4275,12 @@ function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) {
4211
4275
  function BotCard({
4212
4276
  connection,
4213
4277
  busy,
4278
+ repairDisabled,
4214
4279
  actionError,
4215
4280
  testNotice,
4216
4281
  removing,
4217
4282
  onReconnect,
4283
+ onRepairCallback,
4218
4284
  onWorkspaceSave,
4219
4285
  onRequestRemove,
4220
4286
  onConfirmRemove,
@@ -4307,6 +4373,13 @@ function BotCard({
4307
4373
  "aria-busy": busy === "reconnect" ? "true" : void 0,
4308
4374
  "aria-label": `${connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"}${bot.name}`
4309
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"),
4310
4383
  h2(Button5, {
4311
4384
  className: "dim-cardAction",
4312
4385
  kind: "danger",
@@ -4343,11 +4416,13 @@ function BotList(props) {
4343
4416
  { key: bot.botId },
4344
4417
  h2(BotCard, {
4345
4418
  connection: bot,
4346
- 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),
4347
4421
  actionError: props.errorsByBot[bot.botId],
4348
4422
  testNotice: props.testNoticesByBot[bot.botId],
4349
4423
  removing: props.removeTargetId === bot.botId,
4350
4424
  onReconnect: () => props.onReconnect(bot),
4425
+ onRepairCallback: () => props.onRepairCallback(bot),
4351
4426
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
4352
4427
  onRequestRemove: () => props.onRequestRemove(bot),
4353
4428
  onConfirmRemove: () => props.onConfirmRemove(bot),
@@ -4390,11 +4465,12 @@ function mergeFeishuSnapshotState(current, snapshot, { restoreProvisioning = fal
4390
4465
  if (snapshot.revision > 0 && current.revision > snapshot.revision) return current;
4391
4466
  let provisioning = current.provisioning;
4392
4467
  if (!provisioning && restoreProvisioning && snapshot.provisioning) {
4468
+ const submitted = snapshot.provisioning.submitted === true;
4393
4469
  provisioning = {
4394
- phase: snapshot.state === "connecting" ? "connecting" : "qr",
4470
+ phase: submitted || snapshot.state === "connecting" ? "connecting" : "qr",
4395
4471
  ...snapshot.provisioning,
4396
4472
  durationMs: Math.max(1, snapshot.provisioning.expiresAt - now),
4397
- expired: snapshot.provisioning.expiresAt <= now
4473
+ expired: !submitted && snapshot.provisioning.expiresAt <= now
4398
4474
  };
4399
4475
  }
4400
4476
  return {
@@ -4509,7 +4585,15 @@ function FeishuSettingsTab({ rpcCall }) {
4509
4585
  node.focus({ preventScroll: true });
4510
4586
  setFocusBotId(null);
4511
4587
  }, [focusBotId, model.bots]);
4512
- 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;
4513
4597
  setCredentialOpen(false);
4514
4598
  setCredentialError(null);
4515
4599
  setProvisionBusy(true);
@@ -4518,16 +4602,27 @@ function FeishuSettingsTab({ rpcCall }) {
4518
4602
  setModel((current) => ({
4519
4603
  ...current,
4520
4604
  phase: current.phase === "loading" ? "ready" : current.phase,
4521
- provisioning: { phase: "creating" }
4605
+ provisioning: {
4606
+ phase: "creating",
4607
+ operation,
4608
+ ...botId ? { botId } : {},
4609
+ ...botName ? { botName } : {}
4610
+ }
4522
4611
  }));
4523
4612
  try {
4524
4613
  if (replace && previousAttemptId) {
4525
- await invoke(FEISHU_ENDPOINTS.cancelProvisioning, { attemptId: previousAttemptId });
4614
+ try {
4615
+ await invoke(FEISHU_ENDPOINTS.cancelProvisioning, { attemptId: previousAttemptId });
4616
+ } catch {
4617
+ }
4526
4618
  }
4527
4619
  const provision2 = normalizeProvisioning2(await invoke(
4528
- FEISHU_ENDPOINTS.beginProvisioning,
4529
- { locale: "zh-CN" }
4620
+ repairing ? FEISHU_ENDPOINTS.beginCallbackRepair : FEISHU_ENDPOINTS.beginProvisioning,
4621
+ repairing ? { botId } : { locale: "zh-CN" }
4530
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
+ }
4531
4626
  const timestamp7 = Date.now();
4532
4627
  setNow(timestamp7);
4533
4628
  setModel((current) => ({
@@ -4535,20 +4630,34 @@ function FeishuSettingsTab({ rpcCall }) {
4535
4630
  provisioning: {
4536
4631
  phase: "qr",
4537
4632
  ...provision2,
4633
+ ...botName ? { botName } : {},
4538
4634
  durationMs: Math.max(1, provision2.expiresAt - timestamp7),
4539
4635
  expired: false
4540
4636
  }
4541
4637
  }));
4542
- 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");
4543
4639
  } catch (error) {
4544
4640
  setModel((current) => ({
4545
4641
  ...current,
4546
- 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
+ }
4547
4650
  }));
4548
4651
  } finally {
4549
4652
  setProvisionBusy(false);
4550
4653
  }
4551
- }, [announce, invoke, model.provisioning?.attemptId]);
4654
+ }, [
4655
+ announce,
4656
+ invoke,
4657
+ model.provisioning?.attemptId,
4658
+ model.provisioning?.botId,
4659
+ model.provisioning?.botName
4660
+ ]);
4552
4661
  const bindCredentials = React10.useCallback(async ({ identity, secret }) => {
4553
4662
  const snapshotVersion = workspaceFence.beginMutation();
4554
4663
  setCredentialBusy(true);
@@ -4572,23 +4681,64 @@ function FeishuSettingsTab({ rpcCall }) {
4572
4681
  }
4573
4682
  }, [announce, invoke, loadStatus, mergeSnapshot, workspaceFence]);
4574
4683
  const cancelProvisioning = React10.useCallback(async () => {
4575
- 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;
4576
4688
  setProvisionBusy(true);
4577
4689
  try {
4578
- 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
+ }
4579
4718
  setModel((current) => ({ ...current, provisioning: null }));
4580
- 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");
4581
4720
  await loadStatus({ silent: true, restoreProvisioning: false });
4582
- 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");
4583
4728
  } catch (error) {
4584
4729
  setModel((current) => ({
4585
4730
  ...current,
4586
- provisioning: { phase: "error", attemptId, error: presentError3(error) }
4731
+ provisioning: {
4732
+ ...activeProvision,
4733
+ phase: "error",
4734
+ attemptId,
4735
+ error: presentError3(error)
4736
+ }
4587
4737
  }));
4588
4738
  } finally {
4589
4739
  setProvisionBusy(false);
4590
4740
  }
4591
- }, [announce, invoke, loadStatus, model.provisioning?.attemptId, scheduleAnimationFrame]);
4741
+ }, [announce, invoke, loadStatus, model.bots, model.provisioning, scheduleAnimationFrame]);
4592
4742
  const countdownAttemptId = model.provisioning?.attemptId;
4593
4743
  const countdownPhase = model.provisioning?.phase;
4594
4744
  const countdownExpiresAt = model.provisioning?.expiresAt;
@@ -4617,23 +4767,26 @@ function FeishuSettingsTab({ rpcCall }) {
4617
4767
  { attemptId: provision2.attemptId },
4618
4768
  controller.signal
4619
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
+ }
4620
4773
  if (result.status === "connected") {
4621
4774
  const snapshot = await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false });
4622
- const newBot = snapshot?.bots.find((bot) => bot.botId === result.botId);
4775
+ const targetBot = snapshot?.bots.find((bot) => bot.botId === result.botId);
4623
4776
  if (!snapshot) {
4624
- 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");
4625
4778
  }
4626
- if (!newBot?.connected) {
4779
+ if (!targetBot?.connected) {
4627
4780
  setModel((current) => current.provisioning?.attemptId === provision2.attemptId ? { ...current, provisioning: { ...current.provisioning, phase: "connecting" } } : current);
4628
4781
  return;
4629
4782
  }
4630
4783
  setModel((current) => ({ ...current, provisioning: null }));
4631
- 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");
4632
4785
  if (result.botId) setFocusBotId(result.botId);
4633
4786
  return;
4634
4787
  }
4635
4788
  if (result.status === "failed") {
4636
- 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"));
4637
4790
  error.code = "FEISHU_PROVISION_FAILED";
4638
4791
  throw error;
4639
4792
  }
@@ -4658,6 +4811,7 @@ function FeishuSettingsTab({ rpcCall }) {
4658
4811
  setModel((current) => current.provisioning?.attemptId === provision2.attemptId ? {
4659
4812
  ...current,
4660
4813
  provisioning: {
4814
+ ...current.provisioning,
4661
4815
  phase: "error",
4662
4816
  attemptId: provision2.attemptId,
4663
4817
  error: presentError3(error)
@@ -4686,6 +4840,20 @@ function FeishuSettingsTab({ rpcCall }) {
4686
4840
  return next;
4687
4841
  });
4688
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]);
4689
4857
  const reconnectOneBot = React10.useCallback(async (connection) => {
4690
4858
  const { botId, bot } = connection;
4691
4859
  const snapshotVersion = workspaceFence.beginMutation();
@@ -4779,28 +4947,48 @@ function FeishuSettingsTab({ rpcCall }) {
4779
4947
  }
4780
4948
  }, [announce, invoke, loadStatus, mergeSnapshot, scheduleAnimationFrame, setBotBusy, setBotError, workspaceFence]);
4781
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
+ });
4782
4956
  let provisionContent = null;
4783
4957
  if (provision?.phase === "creating") {
4784
- provisionContent = h2(ProvisionProgress, { phase: "creating", busy: provisionBusy });
4958
+ provisionContent = h2(ProvisionProgress, {
4959
+ phase: "creating",
4960
+ provision,
4961
+ busy: provisionBusy
4962
+ });
4785
4963
  } else if (provision?.phase === "qr") {
4786
4964
  provisionContent = h2(QrPane, {
4787
4965
  provision,
4788
4966
  now,
4789
- onRefresh: () => void startProvisioning({ replace: true }),
4967
+ onRefresh: () => void restartProvisioning({ replace: true }),
4790
4968
  onCancel: () => void cancelProvisioning(),
4791
4969
  busy: provisionBusy || model.phase !== "ready"
4792
4970
  });
4793
4971
  } else if (provision?.phase === "connecting") {
4794
4972
  provisionContent = h2(ProvisionProgress, {
4795
4973
  phase: "connecting",
4796
- onCancel: () => void cancelProvisioning(),
4974
+ provision,
4975
+ onCancel: isCallbackRepair(provision) ? void 0 : () => void cancelProvisioning(),
4797
4976
  busy: provisionBusy
4798
4977
  });
4799
4978
  } else if (provision?.phase === "error") {
4800
4979
  provisionContent = h2(ProvisionError2, {
4801
4980
  error: provision.error,
4802
- onRetry: () => void startProvisioning({ replace: Boolean(provision.attemptId) }),
4803
- 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
+ },
4804
4992
  busy: provisionBusy
4805
4993
  });
4806
4994
  }
@@ -4870,7 +5058,9 @@ function FeishuSettingsTab({ rpcCall }) {
4870
5058
  errorsByBot,
4871
5059
  testNoticesByBot,
4872
5060
  removeTargetId,
5061
+ provisioning: provision,
4873
5062
  onReconnect: (bot) => void reconnectOneBot(bot),
5063
+ onRepairCallback: repairCallback,
4874
5064
  onWorkspaceSave: saveWorkspace,
4875
5065
  onRequestRemove: requestRemove,
4876
5066
  onConfirmRemove: (bot) => void confirmRemove(bot),