mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 01:53:21 +08:00
fix(feishu): complete permissions repair flow
This commit is contained in:
parent
1a72e5491f
commit
10ca7eec2c
22 changed files with 574 additions and 392 deletions
|
|
@ -6,6 +6,15 @@ This file records the notable changes in each dsh-im release. Its format follows
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed / 变更
|
||||||
|
|
||||||
|
- 飞书原“修复卡片按钮”操作已更名为“补全权限”,并会同时增量申请读取用户消息内图片或文件所需的 `im:message:readonly`、上传机器人图片或文件所需的 `im:resource`,以及卡片回调;缺权提示会引导用户私聊执行 `/repair`,或在插件页面点击“补全权限”。界面会说明各自用途,并明确确认页只展示应用当前缺少的配置。
|
||||||
|
The former Feishu **Repair card buttons** action is now **Complete permissions**. It incrementally requests `im:message:readonly` for reading images or files in user messages, `im:resource` for uploading bot-sent images or files, and the card callback. The UI explains each purpose and that the confirmation page shows only the app's currently missing items.
|
||||||
|
- 飞书 `/repair` 不再额外区分管理员和普通用户;所有通过当前机器人渠道访问策略的私聊用户都能发起修复,包括使用 `*` 开放访问的手动绑定机器人。
|
||||||
|
Feishu `/repair` no longer defines a separate administrator role. Any direct-message user admitted by the current bot's channel access policy can start repair, including manually bound bots configured with `*` access.
|
||||||
|
- 飞书私聊重复发送普通 `/repair` 时会作废仍在等待授权的旧一次性链接并生成新链接,避免错误账号打开链接后继续复用已消耗的授权码;查询、二维码、验证和取消命令不会意外重启流程,已提交的平台更新也不会并发执行。
|
||||||
|
Repeating bare `/repair` in a Feishu direct chat now invalidates a still-pending one-time authorization link and generates a fresh one, avoiding reuse after the link was opened under the wrong account. Status, QR, verify, and cancel commands do not restart the flow, and a platform update that has already been submitted is never duplicated concurrently.
|
||||||
|
|
||||||
## [2.3.0] - 2026-08-25
|
## [2.3.0] - 2026-08-25
|
||||||
|
|
||||||
### Added / 新增
|
### Added / 新增
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ Connect IM bots to DeepSeek Harness by scanning a QR code, using an App Manifest
|
||||||
|
|
||||||
Other IM platforms can be added through the same channel-adapter structure.
|
Other IM platforms can be added through the same channel-adapter structure.
|
||||||
|
|
||||||
All nine built-in channels can send JPEG, PNG, and WebP images, plus GIFs sent as image files, with optional captions to Harness. Each image is limited to 5 MB, and images in one message are limited to 20 MB in total.
|
All nine built-in channels can send JPEG, PNG, and WebP images, plus GIFs sent as image files, with optional captions to Harness. Each image is limited to 5 MB, and images in one message are limited to 20 MB in total. Downloading images or files from Feishu user messages requires the `im:message:readonly` tenant scope, shown on the confirmation page as **Read direct and group messages**; Feishu currently offers no narrower image-only scope for that download endpoint. Apps created through the built-in QR flow request it by default; for existing or manually connected apps, click **Complete permissions** in the plugin's Feishu settings and scan the QR code to incrementally add that scope, `im:resource` for uploading bot-sent images or files, and the card callback.
|
||||||
|
|
||||||
### Result-file and image delivery
|
### Result-file and image delivery
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ After the model calls the file-return tool, the plugin hands the specified file
|
||||||
| Channel | Platform requirements |
|
| Channel | Platform requirements |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| WeChat | The current binding protocol and conversation must support native file messages; the WeChat API response determines the actual range. |
|
| WeChat | The current binding protocol and conversation must support native file messages; the WeChat API response determines the actual range. |
|
||||||
| Feishu | Feishu's file-upload API requires a non-empty file no larger than the platform's 30 MB limit. The app needs the `im:resource` tenant scope (**Read and upload images or other files**). Apps created through the built-in QR flow request it by default; existing or manually connected apps still need it added and approved. The Feishu developer console currently has no separate `im:resource:upload` scope. |
|
| Feishu | Feishu's file-upload API requires a non-empty file no larger than the platform's 30 MB limit. The app needs the `im:resource` tenant scope (**Read and upload images or other files**). Apps created through the built-in QR flow request it by default; existing or manually connected apps can add it incrementally through **Complete permissions** or `/repair` in a direct chat, followed by any approval Feishu requires. The Feishu developer console currently has no separate `im:resource:upload` scope. |
|
||||||
| DingTalk | The app needs `qyapi_base`, and the bot must support file messages. The current OAPI and bot capability determine the accepted formats and sizes. |
|
| DingTalk | The app needs `qyapi_base`, and the bot must support file messages. The current OAPI and bot capability determine the accepted formats and sizes. |
|
||||||
| WeCom | The app needs media-upload and file-message capability; the WeCom API response determines the actual range. |
|
| WeCom | The app needs media-upload and file-message capability; the WeCom API response determines the actual range. |
|
||||||
| QQ | The bot needs file-message capability and remains subject to QQ's daily upload quota; the bot reports when the quota is exhausted. |
|
| QQ | The bot needs file-message capability and remains subject to QQ's daily upload quota; the bot reports when the quota is exhausted. |
|
||||||
|
|
@ -154,6 +154,7 @@ Each WhatsApp bot also has its own access mode. Existing bots migrate to **Only
|
||||||
| `/batch` | Start batch input in a direct chat and collect up to 10 text messages. |
|
| `/batch` | Start batch input in a direct chat and collect up to 10 text messages. |
|
||||||
| `/send` | Submit the collected messages, in order, as one input. |
|
| `/send` | Submit the collected messages, in order, as one input. |
|
||||||
| `/cancel` | Cancel batch input and discard its collected messages. |
|
| `/cancel` | Cancel batch input and discard its collected messages. |
|
||||||
|
| `/repair` | In a Feishu direct chat, incrementally repair the card callback and permissions required to read and upload message images or files. |
|
||||||
| `/compact` | Immediately compact older context in the Session bound to the current chat. |
|
| `/compact` | Immediately compact older context in the Session bound to the current chat. |
|
||||||
| `/workspace <absolute workspace path>` | Switch the current bot's Harness workspace. |
|
| `/workspace <absolute workspace path>` | Switch the current bot's Harness workspace. |
|
||||||
| `/workspacelist` | List workspace absolute paths that still exist on the current Harness Host. |
|
| `/workspacelist` | List workspace absolute paths that still exist on the current Harness Host. |
|
||||||
|
|
@ -182,6 +183,7 @@ If the Slack desktop app has no native Slash Command registered with the same na
|
||||||
- `/stop` and `/steer` control only a running task started by this chat. Even when multiple chats bind the same Session, they do not intentionally control another chat's task. `/stop` does not delete the Session or its history, preserves queued work that has not started, and is safe to repeat.
|
- `/stop` and `/steer` control only a running task started by this chat. Even when multiple chats bind the same Session, they do not intentionally control another chat's task. `/stop` does not delete the Session or its history, preserves queued work that has not started, and is safe to repeat.
|
||||||
- `/steer` accepts text only, including multiple lines. It neither creates another Session nor starts a second task. Send an ordinary message when no task is running; while an approval or question is pending, answer it first or use `/stop`.
|
- `/steer` accepts text only, including multiple lines. It neither creates another Session nor starts a second task. Send an ordinary message when no task is running; while an approval or question is pending, answer it first or use `/stop`.
|
||||||
- `/batch`, `/send`, and `/cancel` are available only in a direct chat with the bot. After `/batch`, subsequent text-only messages are held temporarily, up to 10 messages. The tenth is collected and prompts you to submit; later messages are rejected and the batch is never submitted automatically. `/send` processes the collected messages in their original order as one input, while `/cancel` discards them. Images, files, and other commands are not collected. An unsubmitted batch is lost if the bot restarts. Ordinary chat behavior is unchanged when batch input is not active.
|
- `/batch`, `/send`, and `/cancel` are available only in a direct chat with the bot. After `/batch`, subsequent text-only messages are held temporarily, up to 10 messages. The tenth is collected and prompts you to submit; later messages are rejected and the batch is never submitted automatically. `/send` processes the collected messages in their original order as one input, while `/cancel` discards them. Images, files, and other commands are not collected. An unsubmitted batch is lost if the bot restarts. Ordinary chat behavior is unchanged when batch input is not active.
|
||||||
|
- Feishu `/repair` is available only in a direct chat and follows the current bot's channel access policy just like every other command; the plugin defines no separate administrator role. It incrementally adds up to `card.action.trigger`, `im:message:readonly`, and `im:resource`, while the confirmation page shows only items the app is currently missing. The authorization page must be opened by an account that can access the target app in Feishu Open Platform. Bare `/repair` starts repair; if an older attempt is still awaiting authorization, it invalidates that one-time link before generating a new one. Use `/repair qr` for the current link's QR code, `/repair status` to inspect the attempt, `/repair verify` to refresh verification, and `/repair cancel` to cancel it; none of these four supplemental commands starts another authorization. Once Feishu has accepted the update and the bot is waiting for the test-button callback, a second repair is not started concurrently.
|
||||||
- `/compact` acts only on the Harness Session already bound to the current chat and is never sent to the model. The bot reports the applicable status when the chat has no Session yet, the Session is generating a reply, or there is no compactable history.
|
- `/compact` acts only on the Harness Session already bound to the current chat and is never sent to the model. The bot reports the applicable status when the chat has no Session yet, the Session is generating a reply, or there is no compactable history.
|
||||||
- The path must be an existing absolute directory. The bot returns an actionable error and the correct usage when validation fails.
|
- The path must be an existing absolute directory. The bot returns an actionable error and the correct usage when validation fails.
|
||||||
- `/workspacelist` takes no arguments. It combines the Harness global registry with the current bot's path. When that current path still exists and is safe to display, it appears first and is marked as current. Any listed path can be copied directly into `/workspace`.
|
- `/workspacelist` takes no arguments. It combines the Harness global registry with the current bot's path. When that current path still exists and is safe to display, it appears first and is marked as current. Any listed path can be copied directly into `/workspace`.
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ Connect IM bots to DeepSeek Harness by scanning a QR code, using an App Manifest
|
||||||
|
|
||||||
其他 IM 平台可继续按同一渠道适配器结构接入。
|
其他 IM 平台可继续按同一渠道适配器结构接入。
|
||||||
|
|
||||||
九个内置渠道均支持把 JPEG、PNG、WebP 图片,以及以图片文件方式发送的 GIF,连同可选文字说明发送给 Harness;单张图片上限为 5 MB,单条消息中的图片总大小上限为 20 MB。
|
九个内置渠道均支持把 JPEG、PNG、WebP 图片,以及以图片文件方式发送的 GIF,连同可选文字说明发送给 Harness;单张图片上限为 5 MB,单条消息中的图片总大小上限为 20 MB。飞书下载用户消息中的图片或文件需要租户权限 `im:message:readonly`,确认页将其显示为“获取单聊、群组消息”;飞书目前没有为该下载接口提供仅限图片的更窄权限。扫码新建的应用会默认申请;已有或手动绑定的应用可私聊机器人执行 `/repair`,或在插件页面点击“补全权限”,扫码增量补全该权限、上传机器人图片或文件所需的 `im:resource`,以及卡片回调。
|
||||||
|
|
||||||
### 结果文件与图片回传
|
### 结果文件与图片回传
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ Connect IM bots to DeepSeek Harness by scanning a QR code, using an App Manifest
|
||||||
| 渠道 | 平台要求 |
|
| 渠道 | 平台要求 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 微信 | 当前绑定协议和会话需支持原生文件消息,实际可发送范围以微信接口返回为准。 |
|
| 微信 | 当前绑定协议和会话需支持原生文件消息,实际可发送范围以微信接口返回为准。 |
|
||||||
| 飞书 | 飞书文件上传接口要求文件非空且不超过平台 30 MB;应用需有租户权限 `im:resource`(“读取与上传图片或文件资源”)。内置扫码流程新建应用时默认申请该权限;已有或手动绑定的应用仍需在开发者后台添加并完成必要审批。飞书开发者后台当前没有单独的 `im:resource:upload` 权限。 |
|
| 飞书 | 飞书文件上传接口要求文件非空且不超过平台 30 MB;应用需有租户权限 `im:resource`(“读取与上传图片或文件资源”)。内置扫码流程新建应用时默认申请该权限;已有或手动绑定的应用可通过“补全权限”或私聊 `/repair` 增量补全并完成飞书要求的审批。飞书开发者后台当前没有单独的 `im:resource:upload` 权限。 |
|
||||||
| 钉钉 | 应用需开通 `qyapi_base`,机器人需具备文件消息能力;实际格式和大小以当前 OAPI 与机器人能力返回为准。 |
|
| 钉钉 | 应用需开通 `qyapi_base`,机器人需具备文件消息能力;实际格式和大小以当前 OAPI 与机器人能力返回为准。 |
|
||||||
| 企业微信 | 应用需具备素材上传和文件消息能力,实际可发送范围以企业微信接口返回为准。 |
|
| 企业微信 | 应用需具备素材上传和文件消息能力,实际可发送范围以企业微信接口返回为准。 |
|
||||||
| QQ | 机器人需具备文件消息能力,并受 QQ 当日文件上传配额约束;额度耗尽时会明确提示稍后重试。 |
|
| QQ | 机器人需具备文件消息能力,并受 QQ 当日文件上传配额约束;额度耗尽时会明确提示稍后重试。 |
|
||||||
|
|
@ -157,6 +157,7 @@ dsh web
|
||||||
| `/batch` | 在私聊中开启批量输入,最多收集 10 条纯文字消息。 |
|
| `/batch` | 在私聊中开启批量输入,最多收集 10 条纯文字消息。 |
|
||||||
| `/send` | 将已收集的消息按原顺序作为一次输入提交。 |
|
| `/send` | 将已收集的消息按原顺序作为一次输入提交。 |
|
||||||
| `/cancel` | 取消批量输入并丢弃已收集的消息。 |
|
| `/cancel` | 取消批量输入并丢弃已收集的消息。 |
|
||||||
|
| `/repair` | 在飞书私聊中增量修复卡片回调,并补全读取及上传消息图片或文件所需的权限。 |
|
||||||
| `/compact` | 立即压缩当前聊天绑定会话的较早上下文。 |
|
| `/compact` | 立即压缩当前聊天绑定会话的较早上下文。 |
|
||||||
| `/workspace <工作区绝对路径>` | 切换当前机器人的 Harness 工作区。 |
|
| `/workspace <工作区绝对路径>` | 切换当前机器人的 Harness 工作区。 |
|
||||||
| `/workspacelist` | 列出当前 Harness Host 上仍然存在的工作区绝对路径。 |
|
| `/workspacelist` | 列出当前 Harness Host 上仍然存在的工作区绝对路径。 |
|
||||||
|
|
@ -185,6 +186,7 @@ Slack 桌面端若未注册同名的原生 Slash Command,会拦截直接以 `/
|
||||||
- `/stop` 和 `/steer` 只控制当前聊天自己发起的运行任务,即使多个聊天绑定同一个 Session,也不会有意控制其他聊天的任务。`/stop` 不删除会话或历史,并保留尚未开始的排队消息;重复发送是安全的。
|
- `/stop` 和 `/steer` 只控制当前聊天自己发起的运行任务,即使多个聊天绑定同一个 Session,也不会有意控制其他聊天的任务。`/stop` 不删除会话或历史,并保留尚未开始的排队消息;重复发送是安全的。
|
||||||
- `/steer` 只接受文字,可包含多行;它不会创建新会话或第二个任务。没有运行任务时请直接发送普通消息;等待审批或问题回答时请先处理交互,或使用 `/stop`。
|
- `/steer` 只接受文字,可包含多行;它不会创建新会话或第二个任务。没有运行任务时请直接发送普通消息;等待审批或问题回答时请先处理交互,或使用 `/stop`。
|
||||||
- `/batch`、`/send` 和 `/cancel` 仅在与机器人的私聊中可用。发送 `/batch` 后,接下来的纯文字消息会暂存,最多 10 条;第 10 条仍会收录并提示提交,之后的消息不会收录,也不会自动提交。发送 `/send` 后,机器人会按原顺序将整批内容作为一次输入处理;发送 `/cancel` 会直接丢弃当前批次。图片、文件和其他命令不会被收录。机器人重启会丢失尚未提交的批次。未进入批量输入模式时,普通聊天流程不变。
|
- `/batch`、`/send` 和 `/cancel` 仅在与机器人的私聊中可用。发送 `/batch` 后,接下来的纯文字消息会暂存,最多 10 条;第 10 条仍会收录并提示提交,之后的消息不会收录,也不会自动提交。发送 `/send` 后,机器人会按原顺序将整批内容作为一次输入处理;发送 `/cancel` 会直接丢弃当前批次。图片、文件和其他命令不会被收录。机器人重启会丢失尚未提交的批次。未进入批量输入模式时,普通聊天流程不变。
|
||||||
|
- 飞书 `/repair` 仅在私聊中可用,并与其他命令一样只服从当前飞书机器人的渠道访问策略;插件不另行区分管理员和普通用户。它最多增量补全 `card.action.trigger`、`im:message:readonly` 和 `im:resource`,确认页只显示当前应用缺少的项。授权页必须由在飞书开放平台中有权访问目标应用的账号打开。普通 `/repair` 会启动修复;若旧任务仍在等待授权,会先作废旧的一次性链接再生成新链接。发送 `/repair qr` 获取当前链接的二维码,`/repair status` 查询当前任务,`/repair verify` 重新查询验证状态,`/repair cancel` 取消任务;这四个补充命令均不会另起授权。平台已接受更新、正在等待测试按钮回调时,不会并发启动第二次修复。
|
||||||
- `/compact` 只作用于当前聊天已经绑定的 Harness 会话,不会把命令发送给模型。当前聊天尚未创建会话、会话正在生成回复或没有可压缩历史时,机器人会直接返回对应状态。
|
- `/compact` 只作用于当前聊天已经绑定的 Harness 会话,不会把命令发送给模型。当前聊天尚未创建会话、会话正在生成回复或没有可压缩历史时,机器人会直接返回对应状态。
|
||||||
- 只接受已经存在的绝对目录;路径无效时机器人会返回具体提示和正确用法。
|
- 只接受已经存在的绝对目录;路径无效时机器人会返回具体提示和正确用法。
|
||||||
- `/workspacelist` 不需要参数。它合并 Harness 全局登记项与当前机器人的路径;当前路径仍存在且可安全显示时会排在首位并标记为“当前”。结果可直接复制到 `/workspace` 命令。
|
- `/workspacelist` 不需要参数。它合并 Harness 全局登记项与当前机器人的路径;当前路径仍存在且可安全显示时会排在首位并标记为“当前”。结果可直接复制到 `/workspace` 命令。
|
||||||
|
|
|
||||||
147
lib/client.js
147
lib/client.js
|
|
@ -422,7 +422,7 @@ var EN = Object.freeze({
|
||||||
"\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",
|
"\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",
|
||||||
"\u5728\u98DE\u4E66\u4E2D\u6253\u5F00": "Open in Feishu",
|
"\u5728\u98DE\u4E66\u4E2D\u6253\u5F00": "Open in Feishu",
|
||||||
"\u53D6\u6D88\u6DFB\u52A0": "Cancel",
|
"\u53D6\u6D88\u6DFB\u52A0": "Cancel",
|
||||||
"\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u4FEE\u590D\u5361\u7247\u6309\u94AE": "Scan with Feishu to repair card buttons",
|
"\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u8865\u5168\u6743\u9650": "Scan with Feishu to complete permissions",
|
||||||
"\u4F7F\u7528\u98DE\u4E66\u786E\u8BA4\u7FA4\u6D88\u606F\u6743\u9650": "Confirm group-message permission with Feishu",
|
"\u4F7F\u7528\u98DE\u4E66\u786E\u8BA4\u7FA4\u6D88\u606F\u6743\u9650": "Confirm group-message permission with Feishu",
|
||||||
"\u626B\u7801\u4F1A\u66F4\u65B0\u73B0\u6709\u98DE\u4E66\u5E94\u7528\uFF0C\u53EA\u589E\u91CF\u5F00\u901A\u201C\u83B7\u53D6\u7FA4\u7EC4\u4E2D\u6240\u6709\u6D88\u606F\u201D\u6743\u9650\uFF1B\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002\u786E\u8BA4\u540E\u4F1A\u81EA\u52A8\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u53D7\u5F71\u54CD\u3002": "Scanning updates the existing Feishu app with only the \u201CRead all messages in associated group chat\u201D scope. It does not create a new app. After confirmation, \u201CRespond to all group messages\u201D is enabled automatically; other bots are unaffected.",
|
"\u626B\u7801\u4F1A\u66F4\u65B0\u73B0\u6709\u98DE\u4E66\u5E94\u7528\uFF0C\u53EA\u589E\u91CF\u5F00\u901A\u201C\u83B7\u53D6\u7FA4\u7EC4\u4E2D\u6240\u6709\u6D88\u606F\u201D\u6743\u9650\uFF1B\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002\u786E\u8BA4\u540E\u4F1A\u81EA\u52A8\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u53D7\u5F71\u54CD\u3002": "Scanning updates the existing Feishu app with only the \u201CRead all messages in associated group chat\u201D scope. It does not create a new app. After confirmation, \u201CRespond to all group messages\u201D is enabled automatically; other bots are unaffected.",
|
||||||
"\u6838\u5BF9\u73B0\u6709\u5E94\u7528\uFF0C\u5E76\u786E\u8BA4\u201C\u83B7\u53D6\u7FA4\u7EC4\u4E2D\u6240\u6709\u6D88\u606F\u201D\u6743\u9650": "Review the existing app and confirm the \u201CRead all messages in associated group chat\u201D permission",
|
"\u6838\u5BF9\u73B0\u6709\u5E94\u7528\uFF0C\u5E76\u786E\u8BA4\u201C\u83B7\u53D6\u7FA4\u7EC4\u4E2D\u6240\u6709\u6D88\u606F\u201D\u6743\u9650": "Review the existing app and confirm the \u201CRead all messages in associated group chat\u201D permission",
|
||||||
|
|
@ -433,18 +433,20 @@ var EN = Object.freeze({
|
||||||
"\u6743\u9650\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u542F\u7528\u5168\u90E8\u6D88\u606F\u6A21\u5F0F\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 permission update was submitted. Enabling all-message mode and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.",
|
"\u6743\u9650\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u542F\u7528\u5168\u90E8\u6D88\u606F\u6A21\u5F0F\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 permission update was submitted. Enabling all-message mode and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.",
|
||||||
"\u6B63\u5728\u4E3A\u73B0\u6709\u98DE\u4E66\u5E94\u7528\u7533\u8BF7\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002": "Requesting a group-message permission QR code for the existing Feishu app\u2026",
|
"\u6B63\u5728\u4E3A\u73B0\u6709\u98DE\u4E66\u5E94\u7528\u7533\u8BF7\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002": "Requesting a group-message permission QR code for the existing Feishu app\u2026",
|
||||||
"\u7FA4\u6D88\u606F\u6743\u9650\u6CA1\u6709\u5F00\u901A\u5B8C\u6210": "Group-message permission was not granted",
|
"\u7FA4\u6D88\u606F\u6743\u9650\u6CA1\u6709\u5F00\u901A\u5B8C\u6210": "Group-message permission was not granted",
|
||||||
"\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.",
|
"\u626B\u7801\u4F1A\u66F4\u65B0\u73B0\u6709\u98DE\u4E66\u5E94\u7528\uFF0C\u6700\u591A\u589E\u91CF\u8865\u5145\u5361\u7247\u6309\u94AE\u56DE\u8C03\u3001\u8BFB\u53D6\u7528\u6237\u6D88\u606F\u5185\u56FE\u7247\u6216\u6587\u4EF6\u6240\u9700\u7684 im:message:readonly\uFF08\u98DE\u4E66\u663E\u793A\u4E3A\u201C\u83B7\u53D6\u5355\u804A\u3001\u7FA4\u7EC4\u6D88\u606F\u201D\uFF09\uFF0C\u4EE5\u53CA\u4E0A\u4F20\u673A\u5668\u4EBA\u56FE\u7247\u6216\u6587\u4EF6\u6240\u9700\u7684 im:resource\uFF1B\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002\u786E\u8BA4\u9875\u53EA\u663E\u793A\u5F53\u524D\u7F3A\u5C11\u9879\uFF0C\u5B8C\u6210\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 up to three missing items: the card-button callback, im:message:readonly for reading images or files in user messages (shown by Feishu as \u201CRead direct and group messages\u201D), and im:resource for uploading images or files sent by the bot. It does not create a new app. The confirmation page shows only missing items; this bot reconnects briefly afterward, while other bots are unaffected.",
|
||||||
"\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",
|
"\u6838\u5BF9\u73B0\u6709\u5E94\u7528\u540D\u79F0\uFF0C\u5E76\u786E\u8BA4\u53EA\u65B0\u589E\u5F53\u524D\u7F3A\u5C11\u7684\u4E0A\u8FF0\u914D\u7F6E": "Review the existing app name and confirm that only the missing items described above are added",
|
||||||
"\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",
|
"\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u6743\u9650\u4E0E\u56DE\u8C03\u8865\u5168\u5B8C\u6210": "Keep this page open until permissions and the callback are complete",
|
||||||
"\u53D6\u6D88\u4FEE\u590D": "Cancel repair",
|
"\u53D6\u6D88\u8865\u5168": "Cancel setup",
|
||||||
"\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u5B8C\u6210\u5361\u7247\u6309\u94AE\u4FEE\u590D": "Confirmed. Finishing card-button repair",
|
"\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u5B8C\u6210\u6743\u9650\u4E0E\u56DE\u8C03\u914D\u7F6E": "Confirmed. Completing permissions and callback setup",
|
||||||
"\u6B63\u5728\u51C6\u5907\u4FEE\u590D\u4E8C\u7EF4\u7801": "Preparing the repair QR code",
|
"\u6B63\u5728\u51C6\u5907\u6743\u9650\u8865\u5168\u4E8C\u7EF4\u7801": "Preparing the permission-completion QR code",
|
||||||
"\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.",
|
"\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u4FDD\u5B58\u6743\u9650\u3001\u9A8C\u8BC1\u5361\u7247\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. Saving permissions, verifying the card callback, and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.",
|
||||||
"\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",
|
"\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",
|
||||||
"\u5361\u7247\u6309\u94AE\u6CA1\u6709\u4FEE\u590D\u5B8C\u6210": "Card-button repair did not finish",
|
"\u6743\u9650\u4E0E\u56DE\u8C03\u6CA1\u6709\u8865\u5168\u5B8C\u6210": "Permissions and callback setup did not finish",
|
||||||
"\u4FEE\u590D\u5361\u7247\u6309\u94AE": "Repair card buttons",
|
"\u8865\u5168\u6743\u9650": "Complete permissions",
|
||||||
|
"\u8865\u5168\u8303\u56F4": "Completion scope",
|
||||||
|
"\u6700\u591A\u589E\u91CF\u6DFB\u52A0\u5361\u7247\u56DE\u8C03 card.action.trigger\u3001\u8BFB\u53D6\u6D88\u606F\u5185\u56FE\u7247\u6216\u6587\u4EF6\u6240\u9700\u7684 im:message:readonly\uFF08\u98DE\u4E66\u663E\u793A\u4E3A\u201C\u83B7\u53D6\u5355\u804A\u3001\u7FA4\u7EC4\u6D88\u606F\u201D\uFF09\uFF0C\u4EE5\u53CA\u4E0A\u4F20\u673A\u5668\u4EBA\u56FE\u7247\u6216\u6587\u4EF6\u6240\u9700\u7684 im:resource\uFF1B\u786E\u8BA4\u9875\u53EA\u663E\u793A\u5F53\u524D\u7F3A\u5C11\u9879\uFF0C\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002": "Adds up to the missing card callback card.action.trigger, im:message:readonly for reading images or files in messages (shown by Feishu as \u201CRead direct and group messages\u201D), and im:resource for uploading images or files sent by the bot. The confirmation page shows only missing items; no new app is created.",
|
||||||
"\u7B49\u5F85\u626B\u7801\u2026": "Waiting for scan\u2026",
|
"\u7B49\u5F85\u626B\u7801\u2026": "Waiting for scan\u2026",
|
||||||
"\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",
|
"\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u6743\u9650\u8865\u5168\u4E8C\u7EF4\u7801": "Feishu returned a permission-completion QR code for a different bot",
|
||||||
"\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801": "Feishu returned a group-message permission QR code for a different bot",
|
"\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801": "Feishu returned a group-message permission QR code for a different bot",
|
||||||
"\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u4FEE\u590D\u4FE1\u606F\u7F3A\u5C11 botId": "Feishu repair status is missing the bot ID",
|
"\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u4FEE\u590D\u4FE1\u606F\u7F3A\u5C11 botId": "Feishu repair status is missing the bot ID",
|
||||||
"\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u5E94\u7528\u66F4\u65B0\u4FE1\u606F\u7F3A\u5C11 botId": "Feishu app-update status is missing the bot ID",
|
"\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u7684\u5E94\u7528\u66F4\u65B0\u4FE1\u606F\u7F3A\u5C11 botId": "Feishu app-update status is missing the bot ID",
|
||||||
|
|
@ -452,16 +454,16 @@ var EN = Object.freeze({
|
||||||
"\u6B64\u673A\u5668\u4EBA": "this bot",
|
"\u6B64\u673A\u5668\u4EBA": "this bot",
|
||||||
"\u7528\u4E8E\u4E3A${botName}\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\u7684\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801": "One-time QR code for granting group-message permission to ${botName}",
|
"\u7528\u4E8E\u4E3A${botName}\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\u7684\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801": "One-time QR code for granting group-message permission to ${botName}",
|
||||||
"\u6B63\u5728\u4E3A\u300C${botName}\u300D\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650": "Granting group-message permission to \u201C${botName}\u201D",
|
"\u6B63\u5728\u4E3A\u300C${botName}\u300D\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650": "Granting group-message permission to \u201C${botName}\u201D",
|
||||||
'${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.',
|
'${botName ?? "\u673A\u5668\u4EBA"}\u7684\u6743\u9650\u8865\u5168\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002': 'Permission-completion QR code generated for ${botName ?? "bot"}. Scan it with Feishu.',
|
||||||
'${botName ?? "\u673A\u5668\u4EBA"}\u7684\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u786E\u8BA4\u3002': 'Group-message permission QR code generated for ${botName ?? "bot"}. Confirm it with Feishu.',
|
'${botName ?? "\u673A\u5668\u4EBA"}\u7684\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u786E\u8BA4\u3002': 'Group-message permission QR code generated for ${botName ?? "bot"}. Confirm it with Feishu.',
|
||||||
"${targetBotName}\u5DF2\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\uFF0C\u5E76\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\u3002": "${targetBotName} now has group-message permission and \u201CRespond to all group messages\u201D is enabled.",
|
"${targetBotName}\u5DF2\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\uFF0C\u5E76\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\u3002": "${targetBotName} now has group-message permission and \u201CRespond to all group messages\u201D is enabled.",
|
||||||
"${targetBot.bot.name}\u5DF2\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\uFF0C\u5E76\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\u3002": "${targetBot.bot.name} now has group-message permission and \u201CRespond to all group messages\u201D is enabled.",
|
"${targetBot.bot.name}\u5DF2\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\uFF0C\u5E76\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\u3002": "${targetBot.bot.name} now has group-message permission and \u201CRespond to all group messages\u201D is enabled.",
|
||||||
"${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.",
|
"${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.",
|
||||||
"\u5DF2\u53D6\u6D88\u5361\u7247\u6309\u94AE\u4FEE\u590D\u3002": "Card-button repair was cancelled.",
|
"\u5DF2\u53D6\u6D88\u8865\u5168\u6743\u9650\u4E0E\u56DE\u8C03\u3002": "Completing permissions and the callback was cancelled.",
|
||||||
"\u5DF2\u53D6\u6D88\u7FA4\u6D88\u606F\u6743\u9650\u6388\u6743\u3002": "Group-message permission authorization was cancelled.",
|
"\u5DF2\u53D6\u6D88\u7FA4\u6D88\u606F\u6743\u9650\u6388\u6743\u3002": "Group-message permission authorization was cancelled.",
|
||||||
"\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",
|
"\u6743\u9650\u4E0E\u56DE\u8C03\u5DF2\u66F4\u65B0\uFF0C\u4F46\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u673A\u5668\u4EBA\u8FDE\u63A5\u72B6\u6001": "Permissions and the callback were updated, but the bot connection could not be confirmed yet",
|
||||||
"\u7FA4\u6D88\u606F\u6743\u9650\u5DF2\u66F4\u65B0\uFF0C\u4F46\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u673A\u5668\u4EBA\u8FDE\u63A5\u72B6\u6001": "The group-message permission was updated, but the bot connection could not be confirmed yet",
|
"\u7FA4\u6D88\u606F\u6743\u9650\u5DF2\u66F4\u65B0\uFF0C\u4F46\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u673A\u5668\u4EBA\u8FDE\u63A5\u72B6\u6001": "The group-message permission was updated, but the bot connection could not be confirmed yet",
|
||||||
"\u98DE\u4E66\u5361\u7247\u6309\u94AE\u4FEE\u590D\u5931\u8D25": "Could not repair the Feishu card buttons",
|
"\u98DE\u4E66\u6743\u9650\u4E0E\u56DE\u8C03\u8865\u5168\u5931\u8D25": "Could not complete Feishu permissions and callback setup",
|
||||||
"\u98DE\u4E66\u7FA4\u6D88\u606F\u6743\u9650\u5F00\u901A\u5931\u8D25": "Could not grant the Feishu group-message permission",
|
"\u98DE\u4E66\u7FA4\u6D88\u606F\u6743\u9650\u5F00\u901A\u5931\u8D25": "Could not grant the Feishu group-message permission",
|
||||||
"\u8BF7\u5148\u5B8C\u6210\u5F53\u524D\u98DE\u4E66\u6388\u6743\u64CD\u4F5C\uFF0C\u518D\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\u3002": "Finish the current Feishu authorization before granting group-message permission.",
|
"\u8BF7\u5148\u5B8C\u6210\u5F53\u524D\u98DE\u4E66\u6388\u6743\u64CD\u4F5C\uFF0C\u518D\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\u3002": "Finish the current Feishu authorization before granting group-message permission.",
|
||||||
"\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA": "Confirmed. Connecting the new bot",
|
"\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA": "Confirmed. Connecting the new bot",
|
||||||
|
|
@ -783,22 +785,22 @@ function translateDynamic(text5) {
|
||||||
if (match) return `Remove ${match[1]} from DeepSeek Harness`;
|
if (match) return `Remove ${match[1]} from DeepSeek Harness`;
|
||||||
match = /^(.+)的飞书授权流程$/.exec(text5);
|
match = /^(.+)的飞书授权流程$/.exec(text5);
|
||||||
if (match) return `Feishu authorization flow for ${match[1]}`;
|
if (match) return `Feishu authorization flow for ${match[1]}`;
|
||||||
match = /^用于修复(.+)卡片按钮的一次性授权二维码$/.exec(text5);
|
match = /^用于为(.+)补全权限与回调的一次性授权二维码$/.exec(text5);
|
||||||
if (match) return `One-time QR code for repairing card buttons for ${match[1]}`;
|
if (match) return `One-time QR code for completing permissions and the callback for ${match[1]}`;
|
||||||
match = /^用于为(.+)开通群消息权限的一次性授权二维码$/.exec(text5);
|
match = /^用于为(.+)开通群消息权限的一次性授权二维码$/.exec(text5);
|
||||||
if (match) return `One-time QR code for granting group-message permission to ${match[1]}`;
|
if (match) return `One-time QR code for granting group-message permission to ${match[1]}`;
|
||||||
match = /^正在修复「(.+)」$/.exec(text5);
|
match = /^正在为「(.+)」补全权限与回调$/.exec(text5);
|
||||||
if (match) return `Repairing \u201C${match[1]}\u201D`;
|
if (match) return `Completing permissions and the callback for \u201C${match[1]}\u201D`;
|
||||||
match = /^正在为「(.+)」开通群消息权限$/.exec(text5);
|
match = /^正在为「(.+)」开通群消息权限$/.exec(text5);
|
||||||
if (match) return `Granting group-message permission to \u201C${match[1]}\u201D`;
|
if (match) return `Granting group-message permission to \u201C${match[1]}\u201D`;
|
||||||
match = /^修复(.+)的卡片按钮$/.exec(text5);
|
match = /^为(.+)补全权限与回调$/.exec(text5);
|
||||||
if (match) return `Repair card buttons for ${match[1]}`;
|
if (match) return `Complete permissions and the callback for ${match[1]}`;
|
||||||
match = /^(.+)的修复二维码已生成,请使用飞书扫码。$/.exec(text5);
|
match = /^(.+)的权限补全二维码已生成,请使用飞书扫码。$/.exec(text5);
|
||||||
if (match) return `Repair QR code generated for ${match[1]}. Scan it with Feishu.`;
|
if (match) return `Permission-completion QR code generated for ${match[1]}. Scan it with Feishu.`;
|
||||||
match = /^(.+)的群消息权限二维码已生成,请使用飞书确认。$/.exec(text5);
|
match = /^(.+)的群消息权限二维码已生成,请使用飞书确认。$/.exec(text5);
|
||||||
if (match) return `Group-message permission QR code generated for ${match[1]}. Confirm it with Feishu.`;
|
if (match) return `Group-message permission QR code generated for ${match[1]}. Confirm it with Feishu.`;
|
||||||
match = /^(.+)的卡片按钮已修复。$/.exec(text5);
|
match = /^(.+)的权限与回调已补全。$/.exec(text5);
|
||||||
if (match) return `Card buttons repaired for ${match[1]}.`;
|
if (match) return `Permissions and the callback completed for ${match[1]}.`;
|
||||||
match = /^(.+)已开通群消息权限,并启用“响应所有群消息”。$/.exec(text5);
|
match = /^(.+)已开通群消息权限,并启用“响应所有群消息”。$/.exec(text5);
|
||||||
if (match) return `${match[1]} now has group-message permission and \u201CRespond to all group messages\u201D is enabled.`;
|
if (match) return `${match[1]} now has group-message permission and \u201CRespond to all group messages\u201D is enabled.`;
|
||||||
match = /^(检查连接|重试连接)(.+)$/.exec(text5);
|
match = /^(检查连接|重试连接)(.+)$/.exec(text5);
|
||||||
|
|
@ -4201,10 +4203,35 @@ var CSS3 = String.raw`
|
||||||
.bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
.bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
||||||
.bxf-healthSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; }
|
.bxf-healthSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; }
|
||||||
.bxf-healthSummary[data-error="true"] { color: var(--bxf-error); }
|
.bxf-healthSummary[data-error="true"] { color: var(--bxf-error); }
|
||||||
.bxf-botActions { flex: none; width: 100%; flex-wrap: wrap; gap: 8px; margin-top: 0; justify-content: flex-end; }
|
.bxf-botActions { position: relative; flex: none; width: 100%; flex-wrap: wrap; gap: 8px; margin-top: 0; justify-content: flex-end; }
|
||||||
.bxf-botActions .bxf-button { flex: none; white-space: nowrap; }
|
.bxf-botActions .bxf-button { flex: none; white-space: nowrap; }
|
||||||
.bxf-botActions .bxf-repairButton { color: var(--bxf-accent); border-color: color-mix(in srgb, var(--bxf-accent) 35%, var(--dsw-alias-border-l2, #dee0e3)); }
|
.bxf-botActions .bxf-repairButton { color: var(--bxf-accent); border-color: color-mix(in srgb, var(--bxf-accent) 35%, var(--dsw-alias-border-l2, #dee0e3)); }
|
||||||
.bxf-botActions .bxf-repairButton:hover:not(:disabled) { background: color-mix(in srgb, var(--bxf-accent) 7%, transparent); }
|
.bxf-botActions .bxf-repairButton:hover:not(:disabled) { background: color-mix(in srgb, var(--bxf-accent) 7%, transparent); }
|
||||||
|
.bxf-repairAction { display: inline-flex; }
|
||||||
|
.bxf-repairTooltip {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: calc(100% + 8px);
|
||||||
|
z-index: 40;
|
||||||
|
width: min(330px, 100%);
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
border: 1px solid var(--dsw-alias-border-l2, #dfe1e5);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--dsw-alias-label-primary, #1f2329);
|
||||||
|
background: var(--dsw-alias-bg-layer-3, #fff);
|
||||||
|
box-shadow: 0 10px 28px rgb(31 35 41 / 16%);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateY(3px);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
|
||||||
|
}
|
||||||
|
.bxf-repairTooltip strong { font-size: 12px; line-height: 17px; font-weight: 650; }
|
||||||
|
.bxf-repairTooltip > span { color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 17px; font-weight: 400; overflow-wrap: anywhere; }
|
||||||
|
.bxf-repairAction:hover .bxf-repairTooltip,
|
||||||
|
.bxf-repairAction:focus-within .bxf-repairTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
|
||||||
|
|
||||||
.bxf-confirm {
|
.bxf-confirm {
|
||||||
border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3);
|
border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3);
|
||||||
|
|
@ -4506,7 +4533,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
||||||
{ className: "bxf-qrFrame dim-qrFrame" },
|
{ className: "bxf-qrFrame dim-qrFrame" },
|
||||||
qrSource && !imageFailed ? h2("img", {
|
qrSource && !imageFailed ? h2("img", {
|
||||||
src: qrSource,
|
src: qrSource,
|
||||||
alt: repairing ? `\u7528\u4E8E\u4FEE\u590D${botName}\u5361\u7247\u6309\u94AE\u7684\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801` : grantingGroupMessages ? `\u7528\u4E8E\u4E3A${botName}\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\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",
|
alt: repairing ? `\u7528\u4E8E\u4E3A${botName}\u8865\u5168\u6743\u9650\u4E0E\u56DE\u8C03\u7684\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801` : grantingGroupMessages ? `\u7528\u4E8E\u4E3A${botName}\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\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",
|
||||||
onError: () => setImageFailed(true)
|
onError: () => setImageFailed(true)
|
||||||
}) : h2(
|
}) : h2(
|
||||||
"div",
|
"div",
|
||||||
|
|
@ -4545,16 +4572,16 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
||||||
"div",
|
"div",
|
||||||
{ className: "bxf-stateLabel dim-stateLabel" },
|
{ className: "bxf-stateLabel dim-stateLabel" },
|
||||||
h2("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }),
|
h2("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }),
|
||||||
h2("span", null, repairing ? `\u6B63\u5728\u4FEE\u590D\u300C${botName}\u300D` : grantingGroupMessages ? `\u6B63\u5728\u4E3A\u300C${botName}\u300D\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650` : "\u6B63\u5728\u6DFB\u52A0\u65B0\u673A\u5668\u4EBA")
|
h2("span", null, repairing ? `\u6B63\u5728\u4E3A\u300C${botName}\u300D\u8865\u5168\u6743\u9650\u4E0E\u56DE\u8C03` : grantingGroupMessages ? `\u6B63\u5728\u4E3A\u300C${botName}\u300D\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650` : "\u6B63\u5728\u6DFB\u52A0\u65B0\u673A\u5668\u4EBA")
|
||||||
),
|
),
|
||||||
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" : grantingGroupMessages ? "\u4F7F\u7528\u98DE\u4E66\u786E\u8BA4\u7FA4\u6D88\u606F\u6743\u9650" : "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u521B\u5EFA\u673A\u5668\u4EBA"),
|
h2("h3", null, expired ? "\u5237\u65B0\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : repairing ? "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u8865\u5168\u6743\u9650" : grantingGroupMessages ? "\u4F7F\u7528\u98DE\u4E66\u786E\u8BA4\u7FA4\u6D88\u606F\u6743\u9650" : "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u521B\u5EFA\u673A\u5668\u4EBA"),
|
||||||
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" : grantingGroupMessages ? "\u626B\u7801\u4F1A\u66F4\u65B0\u73B0\u6709\u98DE\u4E66\u5E94\u7528\uFF0C\u53EA\u589E\u91CF\u5F00\u901A\u201C\u83B7\u53D6\u7FA4\u7EC4\u4E2D\u6240\u6709\u6D88\u606F\u201D\u6743\u9650\uFF1B\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002\u786E\u8BA4\u540E\u4F1A\u81EA\u52A8\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\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"),
|
h2("p", null, repairing ? "\u626B\u7801\u4F1A\u66F4\u65B0\u73B0\u6709\u98DE\u4E66\u5E94\u7528\uFF0C\u6700\u591A\u589E\u91CF\u8865\u5145\u5361\u7247\u6309\u94AE\u56DE\u8C03\u3001\u8BFB\u53D6\u7528\u6237\u6D88\u606F\u5185\u56FE\u7247\u6216\u6587\u4EF6\u6240\u9700\u7684 im:message:readonly\uFF08\u98DE\u4E66\u663E\u793A\u4E3A\u201C\u83B7\u53D6\u5355\u804A\u3001\u7FA4\u7EC4\u6D88\u606F\u201D\uFF09\uFF0C\u4EE5\u53CA\u4E0A\u4F20\u673A\u5668\u4EBA\u56FE\u7247\u6216\u6587\u4EF6\u6240\u9700\u7684 im:resource\uFF1B\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002\u786E\u8BA4\u9875\u53EA\u663E\u793A\u5F53\u524D\u7F3A\u5C11\u9879\uFF0C\u5B8C\u6210\u540E\u6B64\u673A\u5668\u4EBA\u4F1A\u77ED\u6682\u91CD\u8FDE\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u53D7\u5F71\u54CD\u3002" : grantingGroupMessages ? "\u626B\u7801\u4F1A\u66F4\u65B0\u73B0\u6709\u98DE\u4E66\u5E94\u7528\uFF0C\u53EA\u589E\u91CF\u5F00\u901A\u201C\u83B7\u53D6\u7FA4\u7EC4\u4E2D\u6240\u6709\u6D88\u606F\u201D\u6743\u9650\uFF1B\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002\u786E\u8BA4\u540E\u4F1A\u81EA\u52A8\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\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"),
|
||||||
h2(
|
h2(
|
||||||
"ol",
|
"ol",
|
||||||
{ className: "bxf-steps dim-steps" },
|
{ className: "bxf-steps dim-steps" },
|
||||||
h2("li", null, "\u6253\u5F00\u98DE\u4E66\u79FB\u52A8\u7AEF\uFF0C\u4F7F\u7528\u626B\u4E00\u626B\u8BFB\u53D6\u4E8C\u7EF4\u7801"),
|
h2("li", null, "\u6253\u5F00\u98DE\u4E66\u79FB\u52A8\u7AEF\uFF0C\u4F7F\u7528\u626B\u4E00\u626B\u8BFB\u53D6\u4E8C\u7EF4\u7801"),
|
||||||
h2("li", null, repairing ? "\u6838\u5BF9\u73B0\u6709\u5E94\u7528\u540D\u79F0\uFF0C\u5E76\u786E\u8BA4\u53EA\u65B0\u589E\u5361\u7247\u56DE\u8C03" : grantingGroupMessages ? "\u6838\u5BF9\u73B0\u6709\u5E94\u7528\uFF0C\u5E76\u786E\u8BA4\u201C\u83B7\u53D6\u7FA4\u7EC4\u4E2D\u6240\u6709\u6D88\u606F\u201D\u6743\u9650" : "\u6838\u5BF9\u5E94\u7528\u540D\u79F0\u4E0E\u6743\u9650\u8303\u56F4\uFF0C\u5E76\u786E\u8BA4\u521B\u5EFA"),
|
h2("li", null, repairing ? "\u6838\u5BF9\u73B0\u6709\u5E94\u7528\u540D\u79F0\uFF0C\u5E76\u786E\u8BA4\u53EA\u65B0\u589E\u5F53\u524D\u7F3A\u5C11\u7684\u4E0A\u8FF0\u914D\u7F6E" : grantingGroupMessages ? "\u6838\u5BF9\u73B0\u6709\u5E94\u7528\uFF0C\u5E76\u786E\u8BA4\u201C\u83B7\u53D6\u7FA4\u7EC4\u4E2D\u6240\u6709\u6D88\u606F\u201D\u6743\u9650" : "\u6838\u5BF9\u5E94\u7528\u540D\u79F0\u4E0E\u6743\u9650\u8303\u56F4\uFF0C\u5E76\u786E\u8BA4\u521B\u5EFA"),
|
||||||
h2("li", null, repairing ? "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u5361\u7247\u6309\u94AE\u4FEE\u590D\u5B8C\u6210" : grantingGroupMessages ? "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u6743\u9650\u751F\u6548\u5E76\u81EA\u52A8\u5207\u6362\u54CD\u5E94\u65B9\u5F0F" : "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u65B0\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u5C31\u7EEA")
|
h2("li", null, repairing ? "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u6743\u9650\u4E0E\u56DE\u8C03\u8865\u5168\u5B8C\u6210" : grantingGroupMessages ? "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u6743\u9650\u751F\u6548\u5E76\u81EA\u52A8\u5207\u6362\u54CD\u5E94\u65B9\u5F0F" : "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u65B0\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u5C31\u7EEA")
|
||||||
),
|
),
|
||||||
h2(
|
h2(
|
||||||
"div",
|
"div",
|
||||||
|
|
@ -4571,7 +4598,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
||||||
rel: "noopener noreferrer"
|
rel: "noopener noreferrer"
|
||||||
}, h2("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null,
|
}, h2("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null,
|
||||||
!expired ? h2(Button5, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null,
|
!expired ? h2(Button5, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null,
|
||||||
h2(Button5, { onClick: onCancel, disabled: busy }, repairing ? "\u53D6\u6D88\u4FEE\u590D" : grantingGroupMessages ? "\u53D6\u6D88\u6388\u6743" : "\u53D6\u6D88\u6DFB\u52A0")
|
h2(Button5, { onClick: onCancel, disabled: busy }, repairing ? "\u53D6\u6D88\u8865\u5168" : grantingGroupMessages ? "\u53D6\u6D88\u6388\u6743" : "\u53D6\u6D88\u6DFB\u52A0")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -4588,12 +4615,12 @@ function ProvisionProgress({ phase, provision, onCancel, busy }) {
|
||||||
"aria-busy": "true"
|
"aria-busy": "true"
|
||||||
},
|
},
|
||||||
h2("div", { className: "dim-spinner", "aria-hidden": "true" }),
|
h2("div", { className: "dim-spinner", "aria-hidden": "true" }),
|
||||||
h2("h3", null, connecting ? repairing ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u5B8C\u6210\u5361\u7247\u6309\u94AE\u4FEE\u590D" : grantingGroupMessages ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u542F\u7528\u5168\u90E8\u6D88\u606F\u6A21\u5F0F" : "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA" : repairing ? "\u6B63\u5728\u51C6\u5907\u4FEE\u590D\u4E8C\u7EF4\u7801" : grantingGroupMessages ? "\u6B63\u5728\u51C6\u5907\u6743\u9650\u6388\u6743\u4E8C\u7EF4\u7801" : "\u6B63\u5728\u51C6\u5907\u6388\u6743\u4E8C\u7EF4\u7801"),
|
h2("h3", null, connecting ? repairing ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u5B8C\u6210\u6743\u9650\u4E0E\u56DE\u8C03\u914D\u7F6E" : grantingGroupMessages ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u542F\u7528\u5168\u90E8\u6D88\u606F\u6A21\u5F0F" : "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA" : repairing ? "\u6B63\u5728\u51C6\u5907\u6743\u9650\u8865\u5168\u4E8C\u7EF4\u7801" : grantingGroupMessages ? "\u6B63\u5728\u51C6\u5907\u6743\u9650\u6388\u6743\u4E8C\u7EF4\u7801" : "\u6B63\u5728\u51C6\u5907\u6388\u6743\u4E8C\u7EF4\u7801"),
|
||||||
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" : grantingGroupMessages ? "\u6743\u9650\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u4FDD\u5B58\u8BBE\u7F6E\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" : grantingGroupMessages ? "\u6B63\u5728\u4E3A\u73B0\u6709\u98DE\u4E66\u5E94\u7528\u7533\u8BF7\u7FA4\u6D88\u606F\u6743\u9650\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"),
|
h2("p", null, connecting ? repairing ? "\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u4FDD\u5B58\u6743\u9650\u3001\u9A8C\u8BC1\u5361\u7247\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" : grantingGroupMessages ? "\u6743\u9650\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u4FDD\u5B58\u8BBE\u7F6E\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" : grantingGroupMessages ? "\u6B63\u5728\u4E3A\u73B0\u6709\u98DE\u4E66\u5E94\u7528\u7533\u8BF7\u7FA4\u6D88\u606F\u6743\u9650\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"),
|
||||||
connecting && onCancel ? h2(
|
connecting && onCancel ? h2(
|
||||||
"div",
|
"div",
|
||||||
{ className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } },
|
{ className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } },
|
||||||
h2(Button5, { onClick: onCancel, disabled: busy }, repairing ? "\u53D6\u6D88\u4FEE\u590D" : grantingGroupMessages ? "\u53D6\u6D88\u6388\u6743" : "\u53D6\u6D88\u6DFB\u52A0")
|
h2(Button5, { onClick: onCancel, disabled: busy }, repairing ? "\u53D6\u6D88\u8865\u5168" : grantingGroupMessages ? "\u53D6\u6D88\u6388\u6743" : "\u53D6\u6D88\u6DFB\u52A0")
|
||||||
) : null
|
) : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -4609,7 +4636,7 @@ function ProvisionError2({ error, provision, onRetry, onCancel, busy }) {
|
||||||
h2(
|
h2(
|
||||||
"div",
|
"div",
|
||||||
null,
|
null,
|
||||||
h2("h3", null, repairing ? "\u5361\u7247\u6309\u94AE\u6CA1\u6709\u4FEE\u590D\u5B8C\u6210" : grantingGroupMessages ? "\u7FA4\u6D88\u606F\u6743\u9650\u6CA1\u6709\u5F00\u901A\u5B8C\u6210" : "\u65B0\u673A\u5668\u4EBA\u6CA1\u6709\u6DFB\u52A0\u5B8C\u6210"),
|
h2("h3", null, repairing ? "\u6743\u9650\u4E0E\u56DE\u8C03\u6CA1\u6709\u8865\u5168\u5B8C\u6210" : grantingGroupMessages ? "\u7FA4\u6D88\u606F\u6743\u9650\u6CA1\u6709\u5F00\u901A\u5B8C\u6210" : "\u65B0\u673A\u5668\u4EBA\u6CA1\u6709\u6DFB\u52A0\u5B8C\u6210"),
|
||||||
h2("p", null, error.message),
|
h2("p", null, error.message),
|
||||||
error.code ? h2("span", { className: "bxf-errorCode" }, error.code) : null,
|
error.code ? h2("span", { className: "bxf-errorCode" }, error.code) : null,
|
||||||
h2(
|
h2(
|
||||||
|
|
@ -4802,6 +4829,7 @@ function BotCard({
|
||||||
removeButtonRef
|
removeButtonRef
|
||||||
}) {
|
}) {
|
||||||
const { bot, health, state, connected } = connection;
|
const { bot, health, state, connected } = connection;
|
||||||
|
const repairTooltipId = React12.useId();
|
||||||
const stateForDisplay = busy === "reconnect" ? "connecting" : state;
|
const stateForDisplay = busy === "reconnect" ? "connecting" : state;
|
||||||
const tone = stateForDisplay === "connected" ? "success" : stateForDisplay === "connecting" ? "warning" : "error";
|
const tone = stateForDisplay === "connected" ? "success" : stateForDisplay === "connecting" ? "warning" : "error";
|
||||||
const summary = actionError?.message ?? connection.error?.message ?? (connected ? null : health.summary);
|
const summary = actionError?.message ?? connection.error?.message ?? (connected ? null : health.summary);
|
||||||
|
|
@ -4887,13 +4915,28 @@ function BotCard({
|
||||||
"aria-busy": busy === "reconnect" ? "true" : void 0,
|
"aria-busy": busy === "reconnect" ? "true" : void 0,
|
||||||
"aria-label": `${connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"}${bot.name}`
|
"aria-label": `${connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"}${bot.name}`
|
||||||
}, busy === "reconnect" ? connected ? "\u68C0\u67E5\u4E2D\u2026" : "\u6B63\u5728\u8FDE\u63A5\u2026" : connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
|
}, busy === "reconnect" ? connected ? "\u68C0\u67E5\u4E2D\u2026" : "\u6B63\u5728\u8FDE\u63A5\u2026" : connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
|
||||||
h2(Button5, {
|
h2(
|
||||||
className: "bxf-repairButton dim-cardAction",
|
"span",
|
||||||
onClick: onRepairCallback,
|
{ className: "bxf-repairAction" },
|
||||||
disabled: Boolean(busy) || repairDisabled,
|
h2(Button5, {
|
||||||
"aria-busy": busy === "callback-repair" ? "true" : void 0,
|
className: "bxf-repairButton dim-cardAction",
|
||||||
"aria-label": `\u4FEE\u590D${bot.name}\u7684\u5361\u7247\u6309\u94AE`
|
onClick: onRepairCallback,
|
||||||
}, busy === "callback-repair" ? "\u7B49\u5F85\u626B\u7801\u2026" : "\u4FEE\u590D\u5361\u7247\u6309\u94AE"),
|
disabled: Boolean(busy) || repairDisabled,
|
||||||
|
"aria-busy": busy === "callback-repair" ? "true" : void 0,
|
||||||
|
"aria-label": `\u4E3A${bot.name}\u8865\u5168\u6743\u9650\u4E0E\u56DE\u8C03`,
|
||||||
|
"aria-describedby": repairTooltipId
|
||||||
|
}, busy === "callback-repair" ? "\u7B49\u5F85\u626B\u7801\u2026" : "\u8865\u5168\u6743\u9650"),
|
||||||
|
h2(
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
id: repairTooltipId,
|
||||||
|
className: "bxf-repairTooltip",
|
||||||
|
role: "tooltip"
|
||||||
|
},
|
||||||
|
h2("strong", null, "\u8865\u5168\u8303\u56F4"),
|
||||||
|
h2("span", null, "\u6700\u591A\u589E\u91CF\u6DFB\u52A0\u5361\u7247\u56DE\u8C03 card.action.trigger\u3001\u8BFB\u53D6\u6D88\u606F\u5185\u56FE\u7247\u6216\u6587\u4EF6\u6240\u9700\u7684 im:message:readonly\uFF08\u98DE\u4E66\u663E\u793A\u4E3A\u201C\u83B7\u53D6\u5355\u804A\u3001\u7FA4\u7EC4\u6D88\u606F\u201D\uFF09\uFF0C\u4EE5\u53CA\u4E0A\u4F20\u673A\u5668\u4EBA\u56FE\u7247\u6216\u6587\u4EF6\u6240\u9700\u7684 im:resource\uFF1B\u786E\u8BA4\u9875\u53EA\u663E\u793A\u5F53\u524D\u7F3A\u5C11\u9879\uFF0C\u4E0D\u4F1A\u521B\u5EFA\u65B0\u5E94\u7528\u3002")
|
||||||
|
)
|
||||||
|
),
|
||||||
h2(Button5, {
|
h2(Button5, {
|
||||||
className: "dim-cardAction",
|
className: "dim-cardAction",
|
||||||
kind: "danger",
|
kind: "danger",
|
||||||
|
|
@ -5167,7 +5210,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
||||||
targetedUpdate ? { botId } : { locale: "zh-CN" }
|
targetedUpdate ? { botId } : { locale: "zh-CN" }
|
||||||
));
|
));
|
||||||
if (targetedUpdate && (provision2.operation !== operation || provision2.botId !== botId)) {
|
if (targetedUpdate && (provision2.operation !== operation || provision2.botId !== botId)) {
|
||||||
throw new Error(grantingGroupMessages ? "\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801" : "\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u5361\u7247\u4FEE\u590D\u4E8C\u7EF4\u7801");
|
throw new Error(grantingGroupMessages ? "\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801" : "\u98DE\u4E66\u670D\u52A1\u8FD4\u56DE\u4E86\u4E0D\u5339\u914D\u7684\u6743\u9650\u8865\u5168\u4E8C\u7EF4\u7801");
|
||||||
}
|
}
|
||||||
const timestamp7 = Date.now();
|
const timestamp7 = Date.now();
|
||||||
setNow(timestamp7);
|
setNow(timestamp7);
|
||||||
|
|
@ -5181,7 +5224,7 @@ function FeishuSettingsTab({ rpcCall }) {
|
||||||
expired: false
|
expired: false
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
announce(repairing ? `${botName ?? "\u673A\u5668\u4EBA"}\u7684\u4FEE\u590D\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002` : grantingGroupMessages ? `${botName ?? "\u673A\u5668\u4EBA"}\u7684\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u786E\u8BA4\u3002` : "\u6388\u6743\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002");
|
announce(repairing ? `${botName ?? "\u673A\u5668\u4EBA"}\u7684\u6743\u9650\u8865\u5168\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002` : grantingGroupMessages ? `${botName ?? "\u673A\u5668\u4EBA"}\u7684\u7FA4\u6D88\u606F\u6743\u9650\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u786E\u8BA4\u3002` : "\u6388\u6743\u4E8C\u7EF4\u7801\u5DF2\u751F\u6210\uFF0C\u8BF7\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u3002");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setModel((current) => ({
|
setModel((current) => ({
|
||||||
...current,
|
...current,
|
||||||
|
|
@ -5251,20 +5294,20 @@ function FeishuSettingsTab({ rpcCall }) {
|
||||||
expired: false
|
expired: false
|
||||||
}
|
}
|
||||||
} : current);
|
} : current);
|
||||||
announce(grantingGroupMessages ? "\u6743\u9650\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u542F\u7528\u5168\u90E8\u6D88\u606F\u6A21\u5F0F\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" : "\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");
|
announce(grantingGroupMessages ? "\u6743\u9650\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u542F\u7528\u5168\u90E8\u6D88\u606F\u6A21\u5F0F\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" : "\u914D\u7F6E\u5DF2\u63D0\u4EA4\uFF0C\u6B63\u5728\u4FDD\u5B58\u6743\u9650\u3001\u9A8C\u8BC1\u5361\u7247\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");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (result.status === "connected") {
|
if (result.status === "connected") {
|
||||||
const targetBotName = targetBot?.bot.name ?? activeProvision.botName ?? "\u673A\u5668\u4EBA";
|
const targetBotName = targetBot?.bot.name ?? activeProvision.botName ?? "\u673A\u5668\u4EBA";
|
||||||
setModel((current) => ({ ...current, provisioning: null }));
|
setModel((current) => ({ ...current, provisioning: null }));
|
||||||
announce(grantingGroupMessages ? `${targetBotName}\u5DF2\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\uFF0C\u5E76\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\u3002` : `${targetBotName}\u7684\u5361\u7247\u6309\u94AE\u5DF2\u4FEE\u590D\u3002`);
|
announce(grantingGroupMessages ? `${targetBotName}\u5DF2\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\uFF0C\u5E76\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\u3002` : `${targetBotName}\u7684\u6743\u9650\u4E0E\u56DE\u8C03\u5DF2\u8865\u5168\u3002`);
|
||||||
if (activeProvision.botId) setFocusBotId(activeProvision.botId);
|
if (activeProvision.botId) setFocusBotId(activeProvision.botId);
|
||||||
await loadStatus({ silent: true, restoreProvisioning: false });
|
await loadStatus({ silent: true, restoreProvisioning: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setModel((current) => ({ ...current, provisioning: null }));
|
setModel((current) => ({ ...current, provisioning: null }));
|
||||||
announce(repairing ? "\u5DF2\u53D6\u6D88\u5361\u7247\u6309\u94AE\u4FEE\u590D\u3002" : grantingGroupMessages ? "\u5DF2\u53D6\u6D88\u7FA4\u6D88\u606F\u6743\u9650\u6388\u6743\u3002" : "\u5DF2\u53D6\u6D88\u6DFB\u52A0\u673A\u5668\u4EBA\u3002");
|
announce(repairing ? "\u5DF2\u53D6\u6D88\u8865\u5168\u6743\u9650\u4E0E\u56DE\u8C03\u3002" : grantingGroupMessages ? "\u5DF2\u53D6\u6D88\u7FA4\u6D88\u606F\u6743\u9650\u6388\u6743\u3002" : "\u5DF2\u53D6\u6D88\u6DFB\u52A0\u673A\u5668\u4EBA\u3002");
|
||||||
await loadStatus({ silent: true, restoreProvisioning: false });
|
await loadStatus({ silent: true, restoreProvisioning: false });
|
||||||
scheduleAnimationFrame(() => {
|
scheduleAnimationFrame(() => {
|
||||||
if (targetedUpdate && activeProvision.botId) {
|
if (targetedUpdate && activeProvision.botId) {
|
||||||
|
|
@ -5322,19 +5365,19 @@ function FeishuSettingsTab({ rpcCall }) {
|
||||||
const snapshot = await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false });
|
const snapshot = await loadStatus({ signal: controller.signal, silent: true, restoreProvisioning: false });
|
||||||
const targetBot = snapshot?.bots.find((bot) => bot.botId === result.botId);
|
const targetBot = snapshot?.bots.find((bot) => bot.botId === result.botId);
|
||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
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" : isGroupMessagePermission(provision2) ? "\u7FA4\u6D88\u606F\u6743\u9650\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");
|
throw new Error(isCallbackRepair(provision2) ? "\u6743\u9650\u4E0E\u56DE\u8C03\u5DF2\u66F4\u65B0\uFF0C\u4F46\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u673A\u5668\u4EBA\u8FDE\u63A5\u72B6\u6001" : isGroupMessagePermission(provision2) ? "\u7FA4\u6D88\u606F\u6743\u9650\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");
|
||||||
}
|
}
|
||||||
if (!targetBot?.connected) {
|
if (!targetBot?.connected) {
|
||||||
setModel((current) => current.provisioning?.attemptId === provision2.attemptId ? { ...current, provisioning: { ...current.provisioning, phase: "connecting" } } : current);
|
setModel((current) => current.provisioning?.attemptId === provision2.attemptId ? { ...current, provisioning: { ...current.provisioning, phase: "connecting" } } : current);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setModel((current) => ({ ...current, provisioning: null }));
|
setModel((current) => ({ ...current, provisioning: null }));
|
||||||
announce(isCallbackRepair(provision2) ? `${targetBot.bot.name}\u7684\u5361\u7247\u6309\u94AE\u5DF2\u4FEE\u590D\u3002` : isGroupMessagePermission(provision2) ? `${targetBot.bot.name}\u5DF2\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\uFF0C\u5E76\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\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");
|
announce(isCallbackRepair(provision2) ? `${targetBot.bot.name}\u7684\u6743\u9650\u4E0E\u56DE\u8C03\u5DF2\u8865\u5168\u3002` : isGroupMessagePermission(provision2) ? `${targetBot.bot.name}\u5DF2\u5F00\u901A\u7FA4\u6D88\u606F\u6743\u9650\uFF0C\u5E76\u542F\u7528\u201C\u54CD\u5E94\u6240\u6709\u7FA4\u6D88\u606F\u201D\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");
|
||||||
if (result.botId) setFocusBotId(result.botId);
|
if (result.botId) setFocusBotId(result.botId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (result.status === "failed") {
|
if (result.status === "failed") {
|
||||||
const error = new Error(result.message ?? (isCallbackRepair(provision2) ? "\u98DE\u4E66\u5361\u7247\u6309\u94AE\u4FEE\u590D\u5931\u8D25" : isGroupMessagePermission(provision2) ? "\u98DE\u4E66\u7FA4\u6D88\u606F\u6743\u9650\u5F00\u901A\u5931\u8D25" : "\u98DE\u4E66\u5E94\u7528\u521B\u5EFA\u5931\u8D25"));
|
const error = new Error(result.message ?? (isCallbackRepair(provision2) ? "\u98DE\u4E66\u6743\u9650\u4E0E\u56DE\u8C03\u8865\u5168\u5931\u8D25" : isGroupMessagePermission(provision2) ? "\u98DE\u4E66\u7FA4\u6D88\u606F\u6743\u9650\u5F00\u901A\u5931\u8D25" : "\u98DE\u4E66\u5E94\u7528\u521B\u5EFA\u5931\u8D25"));
|
||||||
error.code = "FEISHU_PROVISION_FAILED";
|
error.code = "FEISHU_PROVISION_FAILED";
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
324
lib/index.js
324
lib/index.js
File diff suppressed because one or more lines are too long
|
|
@ -265,7 +265,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
||||||
? h("img", {
|
? h("img", {
|
||||||
src: qrSource,
|
src: qrSource,
|
||||||
alt: repairing
|
alt: repairing
|
||||||
? `用于修复${botName}卡片按钮的一次性授权二维码`
|
? `用于为${botName}补全权限与回调的一次性授权二维码`
|
||||||
: grantingGroupMessages
|
: grantingGroupMessages
|
||||||
? `用于为${botName}开通群消息权限的一次性授权二维码`
|
? `用于为${botName}开通群消息权限的一次性授权二维码`
|
||||||
: "用于新增 DeepSeek Harness 飞书机器人的一次性授权二维码",
|
: "用于新增 DeepSeek Harness 飞书机器人的一次性授权二维码",
|
||||||
|
|
@ -293,31 +293,31 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
||||||
h("div", { className: "bxf-stateLabel dim-stateLabel" },
|
h("div", { className: "bxf-stateLabel dim-stateLabel" },
|
||||||
h("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }),
|
h("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }),
|
||||||
h("span", null, repairing
|
h("span", null, repairing
|
||||||
? `正在修复「${botName}」`
|
? `正在为「${botName}」补全权限与回调`
|
||||||
: grantingGroupMessages
|
: grantingGroupMessages
|
||||||
? `正在为「${botName}」开通群消息权限`
|
? `正在为「${botName}」开通群消息权限`
|
||||||
: "正在添加新机器人")),
|
: "正在添加新机器人")),
|
||||||
h("h3", null, expired
|
h("h3", null, expired
|
||||||
? "刷新二维码后继续"
|
? "刷新二维码后继续"
|
||||||
: repairing
|
: repairing
|
||||||
? "使用飞书扫码修复卡片按钮"
|
? "使用飞书扫码补全权限"
|
||||||
: grantingGroupMessages
|
: grantingGroupMessages
|
||||||
? "使用飞书确认群消息权限"
|
? "使用飞书确认群消息权限"
|
||||||
: "使用飞书扫码创建机器人"),
|
: "使用飞书扫码创建机器人"),
|
||||||
h("p", null, repairing
|
h("p", null, repairing
|
||||||
? "扫码会更新现有飞书应用,只增量补充卡片按钮回调;不会创建新应用。确认后此机器人会短暂重连,其他机器人不受影响。"
|
? "扫码会更新现有飞书应用,最多增量补充卡片按钮回调、读取用户消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”),以及上传机器人图片或文件所需的 im:resource;不会创建新应用。确认页只显示当前缺少项,完成后此机器人会短暂重连,其他机器人不受影响。"
|
||||||
: grantingGroupMessages
|
: grantingGroupMessages
|
||||||
? "扫码会更新现有飞书应用,只增量开通“获取群组中所有消息”权限;不会创建新应用。确认后会自动启用“响应所有群消息”,其他机器人不受影响。"
|
? "扫码会更新现有飞书应用,只增量开通“获取群组中所有消息”权限;不会创建新应用。确认后会自动启用“响应所有群消息”,其他机器人不受影响。"
|
||||||
: "扫码只会新增一个机器人,已接入的机器人会继续正常收发消息。"),
|
: "扫码只会新增一个机器人,已接入的机器人会继续正常收发消息。"),
|
||||||
h("ol", { className: "bxf-steps dim-steps" },
|
h("ol", { className: "bxf-steps dim-steps" },
|
||||||
h("li", null, "打开飞书移动端,使用扫一扫读取二维码"),
|
h("li", null, "打开飞书移动端,使用扫一扫读取二维码"),
|
||||||
h("li", null, repairing
|
h("li", null, repairing
|
||||||
? "核对现有应用名称,并确认只新增卡片回调"
|
? "核对现有应用名称,并确认只新增当前缺少的上述配置"
|
||||||
: grantingGroupMessages
|
: grantingGroupMessages
|
||||||
? "核对现有应用,并确认“获取群组中所有消息”权限"
|
? "核对现有应用,并确认“获取群组中所有消息”权限"
|
||||||
: "核对应用名称与权限范围,并确认创建"),
|
: "核对应用名称与权限范围,并确认创建"),
|
||||||
h("li", null, repairing
|
h("li", null, repairing
|
||||||
? "保持本页打开,等待卡片按钮修复完成"
|
? "保持本页打开,等待权限与回调补全完成"
|
||||||
: grantingGroupMessages
|
: grantingGroupMessages
|
||||||
? "保持本页打开,等待权限生效并自动切换响应方式"
|
? "保持本页打开,等待权限生效并自动切换响应方式"
|
||||||
: "保持本页打开,等待新机器人的长连接就绪")),
|
: "保持本页打开,等待新机器人的长连接就绪")),
|
||||||
|
|
@ -336,7 +336,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
||||||
? h(Button, { onClick: onRefresh, disabled: busy }, "换一个二维码")
|
? h(Button, { onClick: onRefresh, disabled: busy }, "换一个二维码")
|
||||||
: null,
|
: null,
|
||||||
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
||||||
? "取消修复"
|
? "取消补全"
|
||||||
: grantingGroupMessages ? "取消授权" : "取消添加")),
|
: grantingGroupMessages ? "取消授权" : "取消添加")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -354,16 +354,16 @@ function ProvisionProgress({ phase, provision, onCancel, busy }) {
|
||||||
h("div", { className: "dim-spinner", "aria-hidden": "true" }),
|
h("div", { className: "dim-spinner", "aria-hidden": "true" }),
|
||||||
h("h3", null, connecting
|
h("h3", null, connecting
|
||||||
? repairing
|
? repairing
|
||||||
? "已确认,正在完成卡片按钮修复"
|
? "已确认,正在完成权限与回调配置"
|
||||||
: grantingGroupMessages
|
: grantingGroupMessages
|
||||||
? "已确认,正在启用全部消息模式"
|
? "已确认,正在启用全部消息模式"
|
||||||
: "已确认,正在连接新机器人"
|
: "已确认,正在连接新机器人"
|
||||||
: repairing
|
: repairing
|
||||||
? "正在准备修复二维码"
|
? "正在准备权限补全二维码"
|
||||||
: grantingGroupMessages ? "正在准备权限授权二维码" : "正在准备授权二维码"),
|
: grantingGroupMessages ? "正在准备权限授权二维码" : "正在准备授权二维码"),
|
||||||
h("p", null, connecting
|
h("p", null, connecting
|
||||||
? repairing
|
? repairing
|
||||||
? "配置已提交,正在验证卡片按钮回调并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
? "配置已提交,正在保存权限、验证卡片回调并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
||||||
: grantingGroupMessages
|
: grantingGroupMessages
|
||||||
? "权限配置已提交,正在保存设置并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
? "权限配置已提交,正在保存设置并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
||||||
: "正在安全保存凭据并检查新机器人的消息通道,其他机器人不会中断。"
|
: "正在安全保存凭据并检查新机器人的消息通道,其他机器人不会中断。"
|
||||||
|
|
@ -375,7 +375,7 @@ function ProvisionProgress({ phase, provision, onCancel, busy }) {
|
||||||
connecting && onCancel
|
connecting && onCancel
|
||||||
? h("div", { className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } },
|
? h("div", { className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } },
|
||||||
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
||||||
? "取消修复"
|
? "取消补全"
|
||||||
: grantingGroupMessages ? "取消授权" : "取消添加"))
|
: grantingGroupMessages ? "取消授权" : "取消添加"))
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
|
@ -388,7 +388,7 @@ function ProvisionError({ error, provision, onRetry, onCancel, busy }) {
|
||||||
h("div", { className: "bxf-inlineError dim-inlineError", role: "alert" },
|
h("div", { className: "bxf-inlineError dim-inlineError", role: "alert" },
|
||||||
h("div", null,
|
h("div", null,
|
||||||
h("h3", null, repairing
|
h("h3", null, repairing
|
||||||
? "卡片按钮没有修复完成"
|
? "权限与回调没有补全完成"
|
||||||
: grantingGroupMessages ? "群消息权限没有开通完成" : "新机器人没有添加完成"),
|
: grantingGroupMessages ? "群消息权限没有开通完成" : "新机器人没有添加完成"),
|
||||||
h("p", null, error.message),
|
h("p", null, error.message),
|
||||||
error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null,
|
error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null,
|
||||||
|
|
@ -564,6 +564,7 @@ export function BotCard({
|
||||||
removeButtonRef,
|
removeButtonRef,
|
||||||
}) {
|
}) {
|
||||||
const { bot, health, state, connected } = connection;
|
const { bot, health, state, connected } = connection;
|
||||||
|
const repairTooltipId = React.useId();
|
||||||
const stateForDisplay = busy === "reconnect"
|
const stateForDisplay = busy === "reconnect"
|
||||||
? "connecting"
|
? "connecting"
|
||||||
: state;
|
: state;
|
||||||
|
|
@ -637,13 +638,22 @@ export function BotCard({
|
||||||
disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : undefined,
|
disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : undefined,
|
||||||
"aria-label": `${connected ? "检查连接" : "重试连接"}${bot.name}`,
|
"aria-label": `${connected ? "检查连接" : "重试连接"}${bot.name}`,
|
||||||
}, busy === "reconnect" ? (connected ? "检查中…" : "正在连接…") : connected ? "检查连接" : "重试连接"),
|
}, busy === "reconnect" ? (connected ? "检查中…" : "正在连接…") : connected ? "检查连接" : "重试连接"),
|
||||||
h(Button, {
|
h("span", { className: "bxf-repairAction" },
|
||||||
className: "bxf-repairButton dim-cardAction",
|
h(Button, {
|
||||||
onClick: onRepairCallback,
|
className: "bxf-repairButton dim-cardAction",
|
||||||
disabled: Boolean(busy) || repairDisabled,
|
onClick: onRepairCallback,
|
||||||
"aria-busy": busy === "callback-repair" ? "true" : undefined,
|
disabled: Boolean(busy) || repairDisabled,
|
||||||
"aria-label": `修复${bot.name}的卡片按钮`,
|
"aria-busy": busy === "callback-repair" ? "true" : undefined,
|
||||||
}, busy === "callback-repair" ? "等待扫码…" : "修复卡片按钮"),
|
"aria-label": `为${bot.name}补全权限与回调`,
|
||||||
|
"aria-describedby": repairTooltipId,
|
||||||
|
}, busy === "callback-repair" ? "等待扫码…" : "补全权限"),
|
||||||
|
h("span", {
|
||||||
|
id: repairTooltipId,
|
||||||
|
className: "bxf-repairTooltip",
|
||||||
|
role: "tooltip",
|
||||||
|
},
|
||||||
|
h("strong", null, "补全范围"),
|
||||||
|
h("span", null, "最多增量添加卡片回调 card.action.trigger、读取消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”),以及上传机器人图片或文件所需的 im:resource;确认页只显示当前缺少项,不会创建新应用。"))),
|
||||||
h(Button, {
|
h(Button, {
|
||||||
className: "dim-cardAction", kind: "danger", onClick: onRequestRemove,
|
className: "dim-cardAction", kind: "danger", onClick: onRequestRemove,
|
||||||
disabled: Boolean(busy), ref: removeButtonRef,
|
disabled: Boolean(busy), ref: removeButtonRef,
|
||||||
|
|
@ -933,7 +943,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
||||||
&& (provision.operation !== operation || provision.botId !== botId)) {
|
&& (provision.operation !== operation || provision.botId !== botId)) {
|
||||||
throw new Error(grantingGroupMessages
|
throw new Error(grantingGroupMessages
|
||||||
? "飞书服务返回了不匹配的群消息权限二维码"
|
? "飞书服务返回了不匹配的群消息权限二维码"
|
||||||
: "飞书服务返回了不匹配的卡片修复二维码");
|
: "飞书服务返回了不匹配的权限补全二维码");
|
||||||
}
|
}
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
setNow(timestamp);
|
setNow(timestamp);
|
||||||
|
|
@ -948,7 +958,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
announce(repairing
|
announce(repairing
|
||||||
? `${botName ?? "机器人"}的修复二维码已生成,请使用飞书扫码。`
|
? `${botName ?? "机器人"}的权限补全二维码已生成,请使用飞书扫码。`
|
||||||
: grantingGroupMessages
|
: grantingGroupMessages
|
||||||
? `${botName ?? "机器人"}的群消息权限二维码已生成,请使用飞书确认。`
|
? `${botName ?? "机器人"}的群消息权限二维码已生成,请使用飞书确认。`
|
||||||
: "授权二维码已生成,请使用飞书扫码。");
|
: "授权二维码已生成,请使用飞书扫码。");
|
||||||
|
|
@ -1032,7 +1042,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
||||||
: current);
|
: current);
|
||||||
announce(grantingGroupMessages
|
announce(grantingGroupMessages
|
||||||
? "权限配置已提交,正在启用全部消息模式并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
? "权限配置已提交,正在启用全部消息模式并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
||||||
: "配置已提交,正在验证卡片按钮回调并重连此机器人;此阶段无法取消,其他机器人不会中断。");
|
: "配置已提交,正在保存权限、验证卡片回调并重连此机器人;此阶段无法取消,其他机器人不会中断。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (result.status === "connected") {
|
if (result.status === "connected") {
|
||||||
|
|
@ -1040,7 +1050,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
||||||
setModel((current) => ({ ...current, provisioning: null }));
|
setModel((current) => ({ ...current, provisioning: null }));
|
||||||
announce(grantingGroupMessages
|
announce(grantingGroupMessages
|
||||||
? `${targetBotName}已开通群消息权限,并启用“响应所有群消息”。`
|
? `${targetBotName}已开通群消息权限,并启用“响应所有群消息”。`
|
||||||
: `${targetBotName}的卡片按钮已修复。`);
|
: `${targetBotName}的权限与回调已补全。`);
|
||||||
if (activeProvision.botId) setFocusBotId(activeProvision.botId);
|
if (activeProvision.botId) setFocusBotId(activeProvision.botId);
|
||||||
await loadStatus({ silent: true, restoreProvisioning: false });
|
await loadStatus({ silent: true, restoreProvisioning: false });
|
||||||
return;
|
return;
|
||||||
|
|
@ -1048,7 +1058,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
||||||
}
|
}
|
||||||
setModel((current) => ({ ...current, provisioning: null }));
|
setModel((current) => ({ ...current, provisioning: null }));
|
||||||
announce(repairing
|
announce(repairing
|
||||||
? "已取消卡片按钮修复。"
|
? "已取消补全权限与回调。"
|
||||||
: grantingGroupMessages ? "已取消群消息权限授权。" : "已取消添加机器人。");
|
: grantingGroupMessages ? "已取消群消息权限授权。" : "已取消添加机器人。");
|
||||||
await loadStatus({ silent: true, restoreProvisioning: false });
|
await loadStatus({ silent: true, restoreProvisioning: false });
|
||||||
scheduleAnimationFrame(() => {
|
scheduleAnimationFrame(() => {
|
||||||
|
|
@ -1117,7 +1127,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
||||||
const targetBot = snapshot?.bots.find((bot) => bot.botId === result.botId);
|
const targetBot = snapshot?.bots.find((bot) => bot.botId === result.botId);
|
||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
throw new Error(isCallbackRepair(provision)
|
throw new Error(isCallbackRepair(provision)
|
||||||
? "卡片按钮已更新,但暂时无法确认机器人连接状态"
|
? "权限与回调已更新,但暂时无法确认机器人连接状态"
|
||||||
: isGroupMessagePermission(provision)
|
: isGroupMessagePermission(provision)
|
||||||
? "群消息权限已更新,但暂时无法确认机器人连接状态"
|
? "群消息权限已更新,但暂时无法确认机器人连接状态"
|
||||||
: "机器人已经创建,但暂时无法确认连接状态");
|
: "机器人已经创建,但暂时无法确认连接状态");
|
||||||
|
|
@ -1130,7 +1140,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
||||||
}
|
}
|
||||||
setModel((current) => ({ ...current, provisioning: null }));
|
setModel((current) => ({ ...current, provisioning: null }));
|
||||||
announce(isCallbackRepair(provision)
|
announce(isCallbackRepair(provision)
|
||||||
? `${targetBot.bot.name}的卡片按钮已修复。`
|
? `${targetBot.bot.name}的权限与回调已补全。`
|
||||||
: isGroupMessagePermission(provision)
|
: isGroupMessagePermission(provision)
|
||||||
? `${targetBot.bot.name}已开通群消息权限,并启用“响应所有群消息”。`
|
? `${targetBot.bot.name}已开通群消息权限,并启用“响应所有群消息”。`
|
||||||
: targetBot
|
: targetBot
|
||||||
|
|
@ -1142,7 +1152,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
||||||
if (result.status === "failed") {
|
if (result.status === "failed") {
|
||||||
const error = new Error(result.message
|
const error = new Error(result.message
|
||||||
?? (isCallbackRepair(provision)
|
?? (isCallbackRepair(provision)
|
||||||
? "飞书卡片按钮修复失败"
|
? "飞书权限与回调补全失败"
|
||||||
: isGroupMessagePermission(provision)
|
: isGroupMessagePermission(provision)
|
||||||
? "飞书群消息权限开通失败"
|
? "飞书群消息权限开通失败"
|
||||||
: "飞书应用创建失败"));
|
: "飞书应用创建失败"));
|
||||||
|
|
|
||||||
|
|
@ -450,10 +450,35 @@ const CSS = String.raw`
|
||||||
.bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
.bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
||||||
.bxf-healthSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; }
|
.bxf-healthSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; }
|
||||||
.bxf-healthSummary[data-error="true"] { color: var(--bxf-error); }
|
.bxf-healthSummary[data-error="true"] { color: var(--bxf-error); }
|
||||||
.bxf-botActions { flex: none; width: 100%; flex-wrap: wrap; gap: 8px; margin-top: 0; justify-content: flex-end; }
|
.bxf-botActions { position: relative; flex: none; width: 100%; flex-wrap: wrap; gap: 8px; margin-top: 0; justify-content: flex-end; }
|
||||||
.bxf-botActions .bxf-button { flex: none; white-space: nowrap; }
|
.bxf-botActions .bxf-button { flex: none; white-space: nowrap; }
|
||||||
.bxf-botActions .bxf-repairButton { color: var(--bxf-accent); border-color: color-mix(in srgb, var(--bxf-accent) 35%, var(--dsw-alias-border-l2, #dee0e3)); }
|
.bxf-botActions .bxf-repairButton { color: var(--bxf-accent); border-color: color-mix(in srgb, var(--bxf-accent) 35%, var(--dsw-alias-border-l2, #dee0e3)); }
|
||||||
.bxf-botActions .bxf-repairButton:hover:not(:disabled) { background: color-mix(in srgb, var(--bxf-accent) 7%, transparent); }
|
.bxf-botActions .bxf-repairButton:hover:not(:disabled) { background: color-mix(in srgb, var(--bxf-accent) 7%, transparent); }
|
||||||
|
.bxf-repairAction { display: inline-flex; }
|
||||||
|
.bxf-repairTooltip {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: calc(100% + 8px);
|
||||||
|
z-index: 40;
|
||||||
|
width: min(330px, 100%);
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
border: 1px solid var(--dsw-alias-border-l2, #dfe1e5);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--dsw-alias-label-primary, #1f2329);
|
||||||
|
background: var(--dsw-alias-bg-layer-3, #fff);
|
||||||
|
box-shadow: 0 10px 28px rgb(31 35 41 / 16%);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateY(3px);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
|
||||||
|
}
|
||||||
|
.bxf-repairTooltip strong { font-size: 12px; line-height: 17px; font-weight: 650; }
|
||||||
|
.bxf-repairTooltip > span { color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 17px; font-weight: 400; overflow-wrap: anywhere; }
|
||||||
|
.bxf-repairAction:hover .bxf-repairTooltip,
|
||||||
|
.bxf-repairAction:focus-within .bxf-repairTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
|
||||||
|
|
||||||
.bxf-confirm {
|
.bxf-confirm {
|
||||||
border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3);
|
border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3);
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ const EN = Object.freeze({
|
||||||
'保持本页打开,等待新机器人的长连接就绪': 'Keep this page open until the bot connection is ready',
|
'保持本页打开,等待新机器人的长连接就绪': 'Keep this page open until the bot connection is ready',
|
||||||
'在飞书中打开': 'Open in Feishu',
|
'在飞书中打开': 'Open in Feishu',
|
||||||
'取消添加': 'Cancel',
|
'取消添加': 'Cancel',
|
||||||
'使用飞书扫码修复卡片按钮': 'Scan with Feishu to repair card buttons',
|
'使用飞书扫码补全权限': 'Scan with Feishu to complete permissions',
|
||||||
'使用飞书确认群消息权限': 'Confirm group-message permission with Feishu',
|
'使用飞书确认群消息权限': 'Confirm group-message permission with Feishu',
|
||||||
'扫码会更新现有飞书应用,只增量开通“获取群组中所有消息”权限;不会创建新应用。确认后会自动启用“响应所有群消息”,其他机器人不受影响。': 'Scanning updates the existing Feishu app with only the “Read all messages in associated group chat” scope. It does not create a new app. After confirmation, “Respond to all group messages” is enabled automatically; other bots are unaffected.',
|
'扫码会更新现有飞书应用,只增量开通“获取群组中所有消息”权限;不会创建新应用。确认后会自动启用“响应所有群消息”,其他机器人不受影响。': 'Scanning updates the existing Feishu app with only the “Read all messages in associated group chat” scope. It does not create a new app. After confirmation, “Respond to all group messages” is enabled automatically; other bots are unaffected.',
|
||||||
'核对现有应用,并确认“获取群组中所有消息”权限': 'Review the existing app and confirm the “Read all messages in associated group chat” permission',
|
'核对现有应用,并确认“获取群组中所有消息”权限': 'Review the existing app and confirm the “Read all messages in associated group chat” permission',
|
||||||
|
|
@ -242,18 +242,20 @@ const EN = Object.freeze({
|
||||||
'权限配置已提交,正在启用全部消息模式并重连此机器人;此阶段无法取消,其他机器人不会中断。': 'The permission update was submitted. Enabling all-message mode and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.',
|
'权限配置已提交,正在启用全部消息模式并重连此机器人;此阶段无法取消,其他机器人不会中断。': 'The permission update was submitted. Enabling all-message mode and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.',
|
||||||
'正在为现有飞书应用申请群消息权限二维码,请稍候。': 'Requesting a group-message permission QR code for the existing Feishu app…',
|
'正在为现有飞书应用申请群消息权限二维码,请稍候。': 'Requesting a group-message permission QR code for the existing Feishu app…',
|
||||||
'群消息权限没有开通完成': 'Group-message permission was not granted',
|
'群消息权限没有开通完成': 'Group-message permission was not granted',
|
||||||
'扫码会更新现有飞书应用,只增量补充卡片按钮回调;不会创建新应用。确认后此机器人会短暂重连,其他机器人不受影响。': '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.',
|
'扫码会更新现有飞书应用,最多增量补充卡片按钮回调、读取用户消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”),以及上传机器人图片或文件所需的 im:resource;不会创建新应用。确认页只显示当前缺少项,完成后此机器人会短暂重连,其他机器人不受影响。': 'Scanning updates the existing Feishu app with up to three missing items: the card-button callback, im:message:readonly for reading images or files in user messages (shown by Feishu as “Read direct and group messages”), and im:resource for uploading images or files sent by the bot. It does not create a new app. The confirmation page shows only missing items; this bot reconnects briefly afterward, while other bots are unaffected.',
|
||||||
'核对现有应用名称,并确认只新增卡片回调': 'Review the existing app name and confirm that only the card callback is added',
|
'核对现有应用名称,并确认只新增当前缺少的上述配置': 'Review the existing app name and confirm that only the missing items described above are added',
|
||||||
'保持本页打开,等待卡片按钮修复完成': 'Keep this page open until card-button repair finishes',
|
'保持本页打开,等待权限与回调补全完成': 'Keep this page open until permissions and the callback are complete',
|
||||||
'取消修复': 'Cancel repair',
|
'取消补全': 'Cancel setup',
|
||||||
'已确认,正在完成卡片按钮修复': 'Confirmed. Finishing card-button repair',
|
'已确认,正在完成权限与回调配置': 'Confirmed. Completing permissions and callback setup',
|
||||||
'正在准备修复二维码': 'Preparing the repair QR code',
|
'正在准备权限补全二维码': 'Preparing the permission-completion QR code',
|
||||||
'配置已提交,正在验证卡片按钮回调并重连此机器人;此阶段无法取消,其他机器人不会中断。': 'The update was submitted. Verifying the card callback and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.',
|
'配置已提交,正在保存权限、验证卡片回调并重连此机器人;此阶段无法取消,其他机器人不会中断。': 'The update was submitted. Saving permissions, verifying the card callback, and reconnecting this bot. This stage cannot be cancelled; other bots will not be interrupted.',
|
||||||
'正在为现有飞书应用申请一次性更新二维码,请稍候。': 'Requesting a one-time update QR code for the existing Feishu app…',
|
'正在为现有飞书应用申请一次性更新二维码,请稍候。': 'Requesting a one-time update QR code for the existing Feishu app…',
|
||||||
'卡片按钮没有修复完成': 'Card-button repair did not finish',
|
'权限与回调没有补全完成': 'Permissions and callback setup did not finish',
|
||||||
'修复卡片按钮': 'Repair card buttons',
|
'补全权限': 'Complete permissions',
|
||||||
|
'补全范围': 'Completion scope',
|
||||||
|
'最多增量添加卡片回调 card.action.trigger、读取消息内图片或文件所需的 im:message:readonly(飞书显示为“获取单聊、群组消息”),以及上传机器人图片或文件所需的 im:resource;确认页只显示当前缺少项,不会创建新应用。': 'Adds up to the missing card callback card.action.trigger, im:message:readonly for reading images or files in messages (shown by Feishu as “Read direct and group messages”), and im:resource for uploading images or files sent by the bot. The confirmation page shows only missing items; no new app is created.',
|
||||||
'等待扫码…': 'Waiting for scan…',
|
'等待扫码…': 'Waiting for scan…',
|
||||||
'飞书服务返回了不匹配的卡片修复二维码': 'Feishu returned a repair QR code for a different bot',
|
'飞书服务返回了不匹配的权限补全二维码': 'Feishu returned a permission-completion QR code for a different bot',
|
||||||
'飞书服务返回了不匹配的群消息权限二维码': 'Feishu returned a group-message permission QR code for a different bot',
|
'飞书服务返回了不匹配的群消息权限二维码': 'Feishu returned a group-message permission QR code for a different bot',
|
||||||
'飞书服务返回的修复信息缺少 botId': 'Feishu repair status is missing the bot ID',
|
'飞书服务返回的修复信息缺少 botId': 'Feishu repair status is missing the bot ID',
|
||||||
'飞书服务返回的应用更新信息缺少 botId': 'Feishu app-update status is missing the bot ID',
|
'飞书服务返回的应用更新信息缺少 botId': 'Feishu app-update status is missing the bot ID',
|
||||||
|
|
@ -261,16 +263,16 @@ const EN = Object.freeze({
|
||||||
'此机器人': 'this bot',
|
'此机器人': 'this bot',
|
||||||
'用于为${botName}开通群消息权限的一次性授权二维码': 'One-time QR code for granting group-message permission to ${botName}',
|
'用于为${botName}开通群消息权限的一次性授权二维码': 'One-time QR code for granting group-message permission to ${botName}',
|
||||||
'正在为「${botName}」开通群消息权限': 'Granting group-message permission to “${botName}”',
|
'正在为「${botName}」开通群消息权限': 'Granting group-message permission to “${botName}”',
|
||||||
'${botName ?? "机器人"}的修复二维码已生成,请使用飞书扫码。': 'Repair QR code generated for ${botName ?? "bot"}. Scan it with Feishu.',
|
'${botName ?? "机器人"}的权限补全二维码已生成,请使用飞书扫码。': 'Permission-completion QR code generated for ${botName ?? "bot"}. Scan it with Feishu.',
|
||||||
'${botName ?? "机器人"}的群消息权限二维码已生成,请使用飞书确认。': 'Group-message permission QR code generated for ${botName ?? "bot"}. Confirm it with Feishu.',
|
'${botName ?? "机器人"}的群消息权限二维码已生成,请使用飞书确认。': 'Group-message permission QR code generated for ${botName ?? "bot"}. Confirm it with Feishu.',
|
||||||
'${targetBotName}已开通群消息权限,并启用“响应所有群消息”。': '${targetBotName} now has group-message permission and “Respond to all group messages” is enabled.',
|
'${targetBotName}已开通群消息权限,并启用“响应所有群消息”。': '${targetBotName} now has group-message permission and “Respond to all group messages” is enabled.',
|
||||||
'${targetBot.bot.name}已开通群消息权限,并启用“响应所有群消息”。': '${targetBot.bot.name} now has group-message permission and “Respond to all group messages” is enabled.',
|
'${targetBot.bot.name}已开通群消息权限,并启用“响应所有群消息”。': '${targetBot.bot.name} now has group-message permission and “Respond to all group messages” is enabled.',
|
||||||
'${targetBot.bot.name}已连接,可以在飞书中开始聊天。': '${targetBot.bot.name} is connected and ready to chat in Feishu.',
|
'${targetBot.bot.name}已连接,可以在飞书中开始聊天。': '${targetBot.bot.name} is connected and ready to chat in Feishu.',
|
||||||
'已取消卡片按钮修复。': 'Card-button repair was cancelled.',
|
'已取消补全权限与回调。': 'Completing permissions and the callback was cancelled.',
|
||||||
'已取消群消息权限授权。': 'Group-message permission authorization was cancelled.',
|
'已取消群消息权限授权。': 'Group-message permission authorization was cancelled.',
|
||||||
'卡片按钮已更新,但暂时无法确认机器人连接状态': 'The card callback was updated, but the bot connection could not be confirmed yet',
|
'权限与回调已更新,但暂时无法确认机器人连接状态': 'Permissions and the callback were updated, but the bot connection could not be confirmed yet',
|
||||||
'群消息权限已更新,但暂时无法确认机器人连接状态': 'The group-message permission was updated, but the bot connection could not be confirmed yet',
|
'群消息权限已更新,但暂时无法确认机器人连接状态': 'The group-message permission was updated, but the bot connection could not be confirmed yet',
|
||||||
'飞书卡片按钮修复失败': 'Could not repair the Feishu card buttons',
|
'飞书权限与回调补全失败': 'Could not complete Feishu permissions and callback setup',
|
||||||
'飞书群消息权限开通失败': 'Could not grant the Feishu group-message permission',
|
'飞书群消息权限开通失败': 'Could not grant the Feishu group-message permission',
|
||||||
'请先完成当前飞书授权操作,再开通群消息权限。': 'Finish the current Feishu authorization before granting group-message permission.',
|
'请先完成当前飞书授权操作,再开通群消息权限。': 'Finish the current Feishu authorization before granting group-message permission.',
|
||||||
'已确认,正在连接新机器人': 'Confirmed. Connecting the new bot',
|
'已确认,正在连接新机器人': 'Confirmed. Connecting the new bot',
|
||||||
|
|
@ -598,22 +600,22 @@ function translateDynamic(text) {
|
||||||
if (match) return `Remove ${match[1]} from DeepSeek Harness`;
|
if (match) return `Remove ${match[1]} from DeepSeek Harness`;
|
||||||
match = /^(.+)的飞书授权流程$/.exec(text);
|
match = /^(.+)的飞书授权流程$/.exec(text);
|
||||||
if (match) return `Feishu authorization flow for ${match[1]}`;
|
if (match) return `Feishu authorization flow for ${match[1]}`;
|
||||||
match = /^用于修复(.+)卡片按钮的一次性授权二维码$/.exec(text);
|
match = /^用于为(.+)补全权限与回调的一次性授权二维码$/.exec(text);
|
||||||
if (match) return `One-time QR code for repairing card buttons for ${match[1]}`;
|
if (match) return `One-time QR code for completing permissions and the callback for ${match[1]}`;
|
||||||
match = /^用于为(.+)开通群消息权限的一次性授权二维码$/.exec(text);
|
match = /^用于为(.+)开通群消息权限的一次性授权二维码$/.exec(text);
|
||||||
if (match) return `One-time QR code for granting group-message permission to ${match[1]}`;
|
if (match) return `One-time QR code for granting group-message permission to ${match[1]}`;
|
||||||
match = /^正在修复「(.+)」$/.exec(text);
|
match = /^正在为「(.+)」补全权限与回调$/.exec(text);
|
||||||
if (match) return `Repairing “${match[1]}”`;
|
if (match) return `Completing permissions and the callback for “${match[1]}”`;
|
||||||
match = /^正在为「(.+)」开通群消息权限$/.exec(text);
|
match = /^正在为「(.+)」开通群消息权限$/.exec(text);
|
||||||
if (match) return `Granting group-message permission to “${match[1]}”`;
|
if (match) return `Granting group-message permission to “${match[1]}”`;
|
||||||
match = /^修复(.+)的卡片按钮$/.exec(text);
|
match = /^为(.+)补全权限与回调$/.exec(text);
|
||||||
if (match) return `Repair card buttons for ${match[1]}`;
|
if (match) return `Complete permissions and the callback for ${match[1]}`;
|
||||||
match = /^(.+)的修复二维码已生成,请使用飞书扫码。$/.exec(text);
|
match = /^(.+)的权限补全二维码已生成,请使用飞书扫码。$/.exec(text);
|
||||||
if (match) return `Repair QR code generated for ${match[1]}. Scan it with Feishu.`;
|
if (match) return `Permission-completion QR code generated for ${match[1]}. Scan it with Feishu.`;
|
||||||
match = /^(.+)的群消息权限二维码已生成,请使用飞书确认。$/.exec(text);
|
match = /^(.+)的群消息权限二维码已生成,请使用飞书确认。$/.exec(text);
|
||||||
if (match) return `Group-message permission QR code generated for ${match[1]}. Confirm it with Feishu.`;
|
if (match) return `Group-message permission QR code generated for ${match[1]}. Confirm it with Feishu.`;
|
||||||
match = /^(.+)的卡片按钮已修复。$/.exec(text);
|
match = /^(.+)的权限与回调已补全。$/.exec(text);
|
||||||
if (match) return `Card buttons repaired for ${match[1]}.`;
|
if (match) return `Permissions and the callback completed for ${match[1]}.`;
|
||||||
match = /^(.+)已开通群消息权限,并启用“响应所有群消息”。$/.exec(text);
|
match = /^(.+)已开通群消息权限,并启用“响应所有群消息”。$/.exec(text);
|
||||||
if (match) return `${match[1]} now has group-message permission and “Respond to all group messages” is enabled.`;
|
if (match) return `${match[1]} now has group-message permission and “Respond to all group messages” is enabled.`;
|
||||||
match = /^(检查连接|重试连接)(.+)$/.exec(text);
|
match = /^(检查连接|重试连接)(.+)$/.exec(text);
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,10 @@ const CARD_OVERLOAD_NOTICE_COOLDOWN_MS = 5_000;
|
||||||
const CARD_DATA_TIMEOUT_MS = 5_000;
|
const CARD_DATA_TIMEOUT_MS = 5_000;
|
||||||
const REPAIR_LINK_WAIT_MS = 15_000;
|
const REPAIR_LINK_WAIT_MS = 15_000;
|
||||||
const REPAIR_POLL_INTERVAL_MS = 1_000;
|
const REPAIR_POLL_INTERVAL_MS = 1_000;
|
||||||
const REPAIR_ACTIVE_STATES = new Set([
|
const REPAIR_AUTHORIZATION_STATES = new Set([
|
||||||
'starting', 'qr_ready', 'polling', 'slow_down', 'domain_switched', 'saving',
|
'starting', 'qr_ready', 'polling', 'slow_down', 'domain_switched',
|
||||||
]);
|
]);
|
||||||
|
const REPAIR_ACTIVE_STATES = new Set([...REPAIR_AUTHORIZATION_STATES, 'saving']);
|
||||||
const REPAIR_TERMINAL_STATES = new Set([
|
const REPAIR_TERMINAL_STATES = new Set([
|
||||||
'succeeded', 'expired', 'cancelled', 'error',
|
'succeeded', 'expired', 'cancelled', 'error',
|
||||||
]);
|
]);
|
||||||
|
|
@ -150,7 +151,7 @@ function artifactFailureText(fileName, error) {
|
||||||
const name = String(fileName ?? t('结果文件')).replace(/[\r\n]+/g, ' ').trim() || t('结果文件');
|
const name = String(fileName ?? t('结果文件')).replace(/[\r\n]+/g, ' ').trim() || t('结果文件');
|
||||||
switch (error?.code) {
|
switch (error?.code) {
|
||||||
case 'artifact-permission-required':
|
case 'artifact-permission-required':
|
||||||
return t('结果文件「{name}」已生成,但机器人缺少飞书文件上传权限。请为应用添加 im:resource 并完成必要审批后重试。', { name });
|
return t('结果文件「{name}」已生成,但机器人缺少飞书文件上传权限 im:resource。请私聊机器人执行 /repair 命令,或者在插件页面点击“补全权限”按钮并扫码。完成飞书要求的发布审批后重试。', { name });
|
||||||
case 'artifact-too-large':
|
case 'artifact-too-large':
|
||||||
return t('结果文件「{name}」超过飞书 30 MB 上限,未发送。', { name });
|
return t('结果文件「{name}」超过飞书 30 MB 上限,未发送。', { name });
|
||||||
case 'artifact-empty':
|
case 'artifact-empty':
|
||||||
|
|
@ -410,7 +411,6 @@ export class FeishuHarnessBridge {
|
||||||
#botOpenId;
|
#botOpenId;
|
||||||
#groupResponseMode;
|
#groupResponseMode;
|
||||||
#repair;
|
#repair;
|
||||||
#repairOwnerOpenIds;
|
|
||||||
#repairAttempt = null;
|
#repairAttempt = null;
|
||||||
#repairMonitorVersion = 0;
|
#repairMonitorVersion = 0;
|
||||||
#repairPollIntervalMs;
|
#repairPollIntervalMs;
|
||||||
|
|
@ -443,7 +443,6 @@ export class FeishuHarnessBridge {
|
||||||
botOpenId,
|
botOpenId,
|
||||||
groupResponseMode = FEISHU_GROUP_RESPONSE_MODES.ALL,
|
groupResponseMode = FEISHU_GROUP_RESPONSE_MODES.ALL,
|
||||||
repair,
|
repair,
|
||||||
repairOwnerOpenIds,
|
|
||||||
repairPollIntervalMs = REPAIR_POLL_INTERVAL_MS,
|
repairPollIntervalMs = REPAIR_POLL_INTERVAL_MS,
|
||||||
repairLinkWaitMs = REPAIR_LINK_WAIT_MS,
|
repairLinkWaitMs = REPAIR_LINK_WAIT_MS,
|
||||||
cardDataTimeoutMs = CARD_DATA_TIMEOUT_MS,
|
cardDataTimeoutMs = CARD_DATA_TIMEOUT_MS,
|
||||||
|
|
@ -480,10 +479,6 @@ export class FeishuHarnessBridge {
|
||||||
this.#botOpenId = nonEmptyString(botOpenId);
|
this.#botOpenId = nonEmptyString(botOpenId);
|
||||||
this.#groupResponseMode = normalizeFeishuGroupResponseMode(groupResponseMode);
|
this.#groupResponseMode = normalizeFeishuGroupResponseMode(groupResponseMode);
|
||||||
this.#repair = repair ?? null;
|
this.#repair = repair ?? null;
|
||||||
const repairOwners = repairOwnerOpenIds ?? allowedSenderOpenIds;
|
|
||||||
this.#repairOwnerOpenIds = new Set(
|
|
||||||
[...(repairOwners ?? [])].filter((value) => typeof value === 'string' && value && value !== '*'),
|
|
||||||
);
|
|
||||||
this.#repairPollIntervalMs = repairPollIntervalMs;
|
this.#repairPollIntervalMs = repairPollIntervalMs;
|
||||||
this.#repairLinkWaitMs = repairLinkWaitMs;
|
this.#repairLinkWaitMs = repairLinkWaitMs;
|
||||||
this.#cardDataTimeoutMs = cardDataTimeoutMs;
|
this.#cardDataTimeoutMs = cardDataTimeoutMs;
|
||||||
|
|
@ -1075,13 +1070,8 @@ export class FeishuHarnessBridge {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const actorOpenId = strictSenderOpenId(event);
|
const actorOpenId = strictSenderOpenId(event);
|
||||||
if (!actorOpenId || !this.#repairOwnerOpenIds.has(actorOpenId)) {
|
if (!actorOpenId) {
|
||||||
await this.#send(
|
await this.#send(event.message.chat_id, t('无法识别当前发送者,未发起修复。'));
|
||||||
event.message.chat_id,
|
|
||||||
this.#repairOwnerOpenIds.size === 0
|
|
||||||
? t('当前机器人没有可验证的接入者身份,不能从聊天发起修复;请先在插件页设置管理员。')
|
|
||||||
: t('此操作只能由机器人接入者在私聊中发起,未进行任何修改。'),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.#repair) {
|
if (!this.#repair) {
|
||||||
|
|
@ -1110,7 +1100,7 @@ export class FeishuHarnessBridge {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (attempt.actorOpenId !== actorOpenId) {
|
if (attempt.actorOpenId !== actorOpenId) {
|
||||||
await this.#send(chatId, t('另一位管理员正在修复该机器人,本次不会显示其授权信息。'));
|
await this.#send(chatId, t('另一位用户正在修复该机器人,本次不会显示其授权信息。'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (operation === 'cancel') {
|
if (operation === 'cancel') {
|
||||||
|
|
@ -1162,21 +1152,53 @@ export class FeishuHarnessBridge {
|
||||||
|
|
||||||
async #startRepair({ actorOpenId, chatId }) {
|
async #startRepair({ actorOpenId, chatId }) {
|
||||||
const previous = this.#repairAttempt;
|
const previous = this.#repairAttempt;
|
||||||
|
let restarted = false;
|
||||||
if (previous && REPAIR_ACTIVE_STATES.has(previous.snapshot.state)) {
|
if (previous && REPAIR_ACTIVE_STATES.has(previous.snapshot.state)) {
|
||||||
if (previous.actorOpenId !== actorOpenId) {
|
if (previous.actorOpenId !== actorOpenId) {
|
||||||
await this.#send(chatId, t('另一位管理员正在修复该机器人,本次不会显示其授权信息。'));
|
await this.#send(chatId, t('另一位用户正在修复该机器人,本次不会显示其授权信息。'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let current;
|
||||||
try {
|
try {
|
||||||
const current = await this.#refreshRepairAttempt(previous);
|
current = await this.#refreshRepairAttempt(previous);
|
||||||
if (REPAIR_ACTIVE_STATES.has(current.state) && previous.verificationUrl) {
|
|
||||||
await this.#sendRepairLink(chatId, previous.verificationUrl, current, { existing: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
await this.#send(chatId, t('暂时无法查询修复状态,请稍后重试。'));
|
await this.#send(chatId, t('暂时无法查询修复状态,请稍后重试。'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Once Feishu has accepted the update, starting another attempt could
|
||||||
|
// race the credential swap and callback probe. Status commands remain
|
||||||
|
// available while that non-cancellable convergence is in progress.
|
||||||
|
if (current.state === 'saving') {
|
||||||
|
await this.#send(chatId, this.#repairStatusText(current));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Feishu launcher links carry one-time user codes. Opening one with the
|
||||||
|
// wrong Open Platform account can consume it even though authorization
|
||||||
|
// did not succeed, so a fresh bare /repair must replace a still-waiting
|
||||||
|
// attempt instead of redisplaying the same unusable URL.
|
||||||
|
if (REPAIR_AUTHORIZATION_STATES.has(current.state)) {
|
||||||
|
let cancelled;
|
||||||
|
try {
|
||||||
|
cancelled = repairSnapshot(
|
||||||
|
await this.#repair.cancel(this.#repairArgs(previous)),
|
||||||
|
{ botId: this.#botId },
|
||||||
|
);
|
||||||
|
previous.snapshot = cancelled;
|
||||||
|
} catch {
|
||||||
|
await this.#send(chatId, t('暂时无法取消旧修复任务,未生成新链接;请稍后重试。'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!REPAIR_TERMINAL_STATES.has(cancelled.state)) {
|
||||||
|
await this.#send(chatId, this.#repairStatusText(cancelled));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
previous.stopped = true;
|
||||||
|
this.#repairMonitorVersion += 1;
|
||||||
|
restarted = cancelled.state === 'cancelled';
|
||||||
|
} else if (REPAIR_TERMINAL_STATES.has(current.state)) {
|
||||||
|
previous.stopped = true;
|
||||||
|
this.#repairMonitorVersion += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let snapshot;
|
let snapshot;
|
||||||
|
|
@ -1225,7 +1247,7 @@ export class FeishuHarnessBridge {
|
||||||
await this.#send(chatId, t('飞书未返回授权链接,已中止本次修复。'));
|
await this.#send(chatId, t('飞书未返回授权链接,已中止本次修复。'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.#sendRepairLink(chatId, attempt.verificationUrl, snapshot);
|
await this.#sendRepairLink(chatId, attempt.verificationUrl, snapshot, { restarted });
|
||||||
this.#monitorRepair(attempt);
|
this.#monitorRepair(attempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1303,15 +1325,15 @@ export class FeishuHarnessBridge {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async #sendRepairLink(chatId, url, snapshot, { existing = false } = {}) {
|
async #sendRepairLink(chatId, url, snapshot, { restarted = false } = {}) {
|
||||||
const remaining = snapshot.remainingSeconds
|
const remaining = snapshot.remainingSeconds
|
||||||
?? (snapshot.expiresAt ? Math.max(0, Math.ceil((snapshot.expiresAt - Date.now()) / 1000)) : null);
|
?? (snapshot.expiresAt ? Math.max(0, Math.ceil((snapshot.expiresAt - Date.now()) / 1000)) : null);
|
||||||
const expiry = remaining === null
|
const expiry = remaining === null
|
||||||
? t('链接为短期有效')
|
? t('链接为短期有效')
|
||||||
: t('链接约 {minutes} 分钟后过期', { minutes: Math.max(1, Math.ceil(remaining / 60)) });
|
: t('链接约 {minutes} 分钟后过期', { minutes: Math.max(1, Math.ceil(remaining / 60)) });
|
||||||
await this.#send(chatId, [
|
await this.#send(chatId, [
|
||||||
existing ? t('已有一个修复任务在等待授权。') : t('🔧 准备修复卡片按钮。'),
|
restarted ? t('旧授权链接已作废,已生成新的修复链接。') : t('🔧 准备补全权限与回调。'),
|
||||||
t('本次只会增量添加 card.action.trigger。请核对确认页只显示这一项;若出现其他权限或事件,请取消。'),
|
t('本次最多增量添加三项:卡片回调 card.action.trigger;飞书显示为“获取单聊、群组消息”的租户权限 im:message:readonly(用于读取用户消息中的图片或文件);以及 im:resource(用于上传机器人发送的图片或文件)。确认页只会显示当前缺少的项;若出现上述范围之外的配置,请取消。'),
|
||||||
'',
|
'',
|
||||||
t('当前设备直接打开:'),
|
t('当前设备直接打开:'),
|
||||||
url,
|
url,
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ function backButton() {
|
||||||
* - presetCatalog: object|null (预设目录, {items,defaultId,_currentId})
|
* - presetCatalog: object|null (预设目录, {items,defaultId,_currentId})
|
||||||
* - modelCatalog: object|null (模型目录, {groups,current})
|
* - modelCatalog: object|null (模型目录, {groups,current})
|
||||||
* Number fallback: 1=续写 2=新会话 3=会话列表 4=状态
|
* Number fallback: 1=续写 2=新会话 3=会话列表 4=状态
|
||||||
* 5=修复 6=帮助.
|
* 5=补全权限 6=帮助.
|
||||||
*/
|
*/
|
||||||
export function menuCard(ctx) {
|
export function menuCard(ctx) {
|
||||||
const {
|
const {
|
||||||
|
|
@ -323,7 +323,7 @@ export function menuCard(ctx) {
|
||||||
|
|
||||||
// 命令与数字兜底说明
|
// 命令与数字兜底说明
|
||||||
elements.push({ tag: 'div', text: markdown(t(
|
elements.push({ tag: 'div', text: markdown(t(
|
||||||
'**数字兜底**\n**1**工作区列表 · **2**新会话 · **3**会话列表 · **4**状态\n**5**🔧修复 · **6**帮助',
|
'**数字兜底**\n**1**工作区列表 · **2**新会话 · **3**会话列表 · **4**状态\n**5**🔧补全权限 · **6**帮助',
|
||||||
)) });
|
)) });
|
||||||
return cardWith(t('🤖 助手中心'), elements);
|
return cardWith(t('🤖 助手中心'), elements);
|
||||||
}
|
}
|
||||||
|
|
@ -521,7 +521,7 @@ export function menuHelpText() {
|
||||||
'🎮 任务控制',
|
'🎮 任务控制',
|
||||||
'/stop 停止当前任务',
|
'/stop 停止当前任务',
|
||||||
'/steer 指令 给 Agent 补充指令',
|
'/steer 指令 给 Agent 补充指令',
|
||||||
'/repair 修复卡片按钮回调',
|
'/repair 补全飞书权限与卡片回调',
|
||||||
].map((line) => t(line)).join('\n');
|
].map((line) => t(line)).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -572,14 +572,14 @@ const HELP_TEXT_COMMANDS = [
|
||||||
'`/batch` — 开启批量输入(仅私聊,最多 10 条文字)',
|
'`/batch` — 开启批量输入(仅私聊,最多 10 条文字)',
|
||||||
'`/send` — 提交当前批次',
|
'`/send` — 提交当前批次',
|
||||||
'`/cancel` — 取消当前批次',
|
'`/cancel` — 取消当前批次',
|
||||||
'`/repair` — 修复卡片按钮',
|
'`/repair` — 补全飞书权限与卡片回调',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
const HELP_NUMBER_FALLBACK = [
|
const HELP_NUMBER_FALLBACK = [
|
||||||
'**💡 数字兜底**',
|
'**💡 数字兜底**',
|
||||||
'回复数字快速操作:',
|
'回复数字快速操作:',
|
||||||
'**1**工作区列表 · **2**新会话 · **3**会话/关注',
|
'**1**工作区列表 · **2**新会话 · **3**会话/关注',
|
||||||
'**4**状态 · **5**修复 · **6**帮助',
|
'**4**状态 · **5**补全权限 · **6**帮助',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
export function helpCard(extraTextLines = []) {
|
export function helpCard(extraTextLines = []) {
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,6 @@ export class FeishuRuntime {
|
||||||
botOpenId: this.#botOpenId,
|
botOpenId: this.#botOpenId,
|
||||||
groupResponseMode: this.#groupResponseMode,
|
groupResponseMode: this.#groupResponseMode,
|
||||||
repair: this.#repair,
|
repair: this.#repair,
|
||||||
repairOwnerOpenIds: new Set(this.#ownerOpenIds.filter((value) => value !== '*')),
|
|
||||||
replyTimeoutMs: this.#replyTimeoutMs,
|
replyTimeoutMs: this.#replyTimeoutMs,
|
||||||
signal,
|
signal,
|
||||||
logger: this.#logger,
|
logger: this.#logger,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ const FEISHU_MISSING_MESSAGE_SCOPE_CODE = 99991672;
|
||||||
const FEISHU_ERROR_BODY_LIMIT = 64 * 1024;
|
const FEISHU_ERROR_BODY_LIMIT = 64 * 1024;
|
||||||
const FEISHU_ERROR_BODY_TIMEOUT_MS = 1_000;
|
const FEISHU_ERROR_BODY_TIMEOUT_MS = 1_000;
|
||||||
const FEISHU_IMAGE_PERMISSION_MESSAGE =
|
const FEISHU_IMAGE_PERMISSION_MESSAGE =
|
||||||
'飞书机器人缺少图片读取权限。请在飞书开放平台为该应用添加 im:message:readonly,发布新版本并完成必要的管理员审批后,再重新发送图片。';
|
'飞书机器人缺少图片读取权限 im:message:readonly(飞书显示为“获取单聊、群组消息”)。请私聊机器人执行 /repair 命令,或者在插件页面点击“补全权限”按钮并扫码。按飞书提示发布新版本、完成必要审批后,再重新发送图片。';
|
||||||
|
|
||||||
export function conversationKey(event) {
|
export function conversationKey(event) {
|
||||||
const chatType = event?.message?.chat_type;
|
const chatType = event?.message?.chat_type;
|
||||||
|
|
|
||||||
|
|
@ -869,22 +869,6 @@ export class MultiBotDshFeishuController {
|
||||||
'Feishu returned credentials for a different account domain.',
|
'Feishu returned credentials for a different account domain.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (record.initiator.actorOpenId && record.initiator.actorOpenId !== ownerOpenId) {
|
|
||||||
throw this.#callbackRepairError(
|
|
||||||
record,
|
|
||||||
'repair_owner_mismatch',
|
|
||||||
'The Feishu repair was confirmed by a different operator.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!target.ownerOpenIds.includes(ALL_VISIBLE_SENDERS)
|
|
||||||
&& !target.ownerOpenIds.includes(ownerOpenId)) {
|
|
||||||
throw this.#callbackRepairError(
|
|
||||||
record,
|
|
||||||
'repair_owner_mismatch',
|
|
||||||
'The Feishu repair operator is not an owner of this configured bot.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
record.stage = 'verifying_identity';
|
record.stage = 'verifying_identity';
|
||||||
let verified;
|
let verified;
|
||||||
try {
|
try {
|
||||||
|
|
@ -1008,7 +992,7 @@ export class MultiBotDshFeishuController {
|
||||||
record.stage = 'awaiting_callback';
|
record.stage = 'awaiting_callback';
|
||||||
try {
|
try {
|
||||||
const proof = await runtime.beginCardActionProbe({
|
const proof = await runtime.beginCardActionProbe({
|
||||||
expectedOperatorOpenId: ownerOpenId,
|
expectedOperatorOpenId: record.initiator.actorOpenId ?? ownerOpenId,
|
||||||
timeoutMs: this.#callbackProbeTimeoutMs,
|
timeoutMs: this.#callbackProbeTimeoutMs,
|
||||||
...(record.initiator.chatId ? { chatId: record.initiator.chatId } : {}),
|
...(record.initiator.chatId ? { chatId: record.initiator.chatId } : {}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { RegistrationManager } from './registration-manager.mjs';
|
import { RegistrationManager } from './registration-manager.mjs';
|
||||||
|
|
||||||
export const CARD_ACTION_CALLBACK = 'card.action.trigger';
|
export const CARD_ACTION_CALLBACK = 'card.action.trigger';
|
||||||
|
export const FEISHU_MESSAGE_READ_SCOPE = 'im:message:readonly';
|
||||||
|
export const FEISHU_RESOURCE_SCOPE = 'im:resource';
|
||||||
export const CALLBACK_REPAIR_OPERATION = 'callback_repair';
|
export const CALLBACK_REPAIR_OPERATION = 'callback_repair';
|
||||||
|
|
||||||
function accountsDomain(domain) {
|
function accountsDomain(domain) {
|
||||||
|
|
@ -71,8 +73,10 @@ export function assertCallbackRepairUrl(value, expectedAppId, domain = 'feishu')
|
||||||
/**
|
/**
|
||||||
* One targeted update attempt for an existing Feishu app. It intentionally
|
* One targeted update attempt for an existing Feishu app. It intentionally
|
||||||
* shares RegistrationManager's polling/state implementation while fixing the
|
* shares RegistrationManager's polling/state implementation while fixing the
|
||||||
* update manifest in one place so callers cannot accidentally add scopes,
|
* update manifest in one place so callers can add only the card callback, the
|
||||||
* events, presets, or createOnly.
|
* message-read scope needed to download user-sent media, and the resource
|
||||||
|
* scope needed to upload bot-sent images/files, without adding unrelated
|
||||||
|
* scopes, events, presets, or createOnly.
|
||||||
*/
|
*/
|
||||||
export class CallbackRepairManager {
|
export class CallbackRepairManager {
|
||||||
#manager;
|
#manager;
|
||||||
|
|
@ -106,6 +110,7 @@ export class CallbackRepairManager {
|
||||||
appId: this.#appId,
|
appId: this.#appId,
|
||||||
addons: {
|
addons: {
|
||||||
preset: false,
|
preset: false,
|
||||||
|
scopes: { tenant: [FEISHU_MESSAGE_READ_SCOPE, FEISHU_RESOURCE_SCOPE] },
|
||||||
callbacks: { items: [CARD_ACTION_CALLBACK] },
|
callbacks: { items: [CARD_ACTION_CALLBACK] },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ export default {
|
||||||
// feishu/bridge.mjs — welcome / help
|
// feishu/bridge.mjs — welcome / help
|
||||||
'北汇星河 AIOS 已连接 DeepSeek Harness。':
|
'北汇星河 AIOS 已连接 DeepSeek Harness。':
|
||||||
'BeiHui XingHe AIOS is connected to DeepSeek Harness.',
|
'BeiHui XingHe AIOS is connected to DeepSeek Harness.',
|
||||||
'/repair 修复卡片按钮回调': '/repair Repair the card-button callback',
|
'/repair 补全飞书权限与卡片回调': '/repair Complete Feishu permissions and the card callback',
|
||||||
'/m(或 /menu) 打开交互卡片菜单': '/m (or /menu) Open the interactive card menu',
|
'/m(或 /menu) 打开交互卡片菜单': '/m (or /menu) Open the interactive card menu',
|
||||||
'/watch [Session ID 或序号] 关注会话,任务完成自动推送':
|
'/watch [Session ID 或序号] 关注会话,任务完成自动推送':
|
||||||
'/watch [Session ID or index] Watch a session; completion is pushed automatically',
|
'/watch [Session ID or index] Watch a session; completion is pushed automatically',
|
||||||
|
|
@ -18,8 +18,8 @@ export default {
|
||||||
'机器人正在移除或已重新接入,无法操作原会话的工作区。':
|
'机器人正在移除或已重新接入,无法操作原会话的工作区。':
|
||||||
'The bot is being removed or has been reconnected; the original session’s workspace cannot be changed.',
|
'The bot is being removed or has been reconnected; the original session’s workspace cannot be changed.',
|
||||||
'操作失败,请稍后重试。': 'The operation failed. Please try again later.',
|
'操作失败,请稍后重试。': 'The operation failed. Please try again later.',
|
||||||
'结果文件「{name}」已生成,但机器人缺少飞书文件上传权限。请为应用添加 im:resource 并完成必要审批后重试。':
|
'结果文件「{name}」已生成,但机器人缺少飞书文件上传权限 im:resource。请私聊机器人执行 /repair 命令,或者在插件页面点击“补全权限”按钮并扫码。完成飞书要求的发布审批后重试。':
|
||||||
'The result file "{name}" was generated, but the bot lacks Feishu file-upload permission. Add the im:resource scope to the app, complete the required approval, and try again.',
|
'The result file "{name}" was generated, but the bot lacks the Feishu file-upload scope im:resource. Run /repair in a direct chat with the bot, or click the “Complete permissions” button on the plugin page and scan the QR code. Complete any publishing approval Feishu requires, then try again.',
|
||||||
'结果文件「{name}」超过飞书 30 MB 上限,未发送。':
|
'结果文件「{name}」超过飞书 30 MB 上限,未发送。':
|
||||||
'The result file "{name}" exceeds the Feishu 30 MB limit and was not sent.',
|
'The result file "{name}" exceeds the Feishu 30 MB limit and was not sent.',
|
||||||
'结果文件「{name}」为空,飞书不允许发送空文件。':
|
'结果文件「{name}」为空,飞书不允许发送空文件。':
|
||||||
|
|
@ -41,20 +41,20 @@ export default {
|
||||||
// feishu/bridge.mjs — repair flow
|
// feishu/bridge.mjs — repair flow
|
||||||
'为避免授权链接暴露,请私聊机器人发送 /repair。':
|
'为避免授权链接暴露,请私聊机器人发送 /repair。':
|
||||||
'To keep the authorization link private, send /repair to the bot in a direct message.',
|
'To keep the authorization link private, send /repair to the bot in a direct message.',
|
||||||
'当前机器人没有可验证的接入者身份,不能从聊天发起修复;请先在插件页设置管理员。':
|
'无法识别当前发送者,未发起修复。':
|
||||||
'This bot has no verifiable owner identity, so repair cannot be started from chat. Set an administrator on the plugin page first.',
|
'The current sender could not be identified, so repair was not started.',
|
||||||
'此操作只能由机器人接入者在私聊中发起,未进行任何修改。':
|
|
||||||
'This action can only be started by the bot owner in a direct message. Nothing was changed.',
|
|
||||||
'当前 Host 版本暂不支持聊天内修复,请先更新插件。':
|
'当前 Host 版本暂不支持聊天内修复,请先更新插件。':
|
||||||
'The current Host version does not support in-chat repair. Update the plugin first.',
|
'The current Host version does not support in-chat repair. Update the plugin first.',
|
||||||
'用法:/repair、/repair qr、/repair status、/repair cancel 或 /repair verify':
|
'用法:/repair、/repair qr、/repair status、/repair cancel 或 /repair verify':
|
||||||
'Usage: /repair, /repair qr, /repair status, /repair cancel, or /repair verify',
|
'Usage: /repair, /repair qr, /repair status, /repair cancel, or /repair verify',
|
||||||
'当前 Runtime 没有可恢复的修复任务记录(机器人可能刚完成密钥更新并重启)。本命令不会启动新的授权;请查看机器人发送的验证结果,确认上一次任务已结束后再发送 /repair。':
|
'当前 Runtime 没有可恢复的修复任务记录(机器人可能刚完成密钥更新并重启)。本命令不会启动新的授权;请查看机器人发送的验证结果,确认上一次任务已结束后再发送 /repair。':
|
||||||
'The current Runtime has no recoverable repair attempt (the bot may have just rotated its secret and restarted). This command starts no new authorization. Check the verification result the bot sent, then send /repair after the previous attempt has finished.',
|
'The current Runtime has no recoverable repair attempt (the bot may have just rotated its secret and restarted). This command starts no new authorization. Check the verification result the bot sent, then send /repair after the previous attempt has finished.',
|
||||||
'另一位管理员正在修复该机器人,本次不会显示其授权信息。':
|
'另一位用户正在修复该机器人,本次不会显示其授权信息。':
|
||||||
'Another administrator is repairing this bot; its authorization info will not be shown this time.',
|
'Another user is repairing this bot; its authorization info will not be shown this time.',
|
||||||
'暂时无法取消修复任务,请稍后重试。':
|
'暂时无法取消修复任务,请稍后重试。':
|
||||||
'Could not cancel the repair task right now. Please try again later.',
|
'Could not cancel the repair task right now. Please try again later.',
|
||||||
|
'暂时无法取消旧修复任务,未生成新链接;请稍后重试。':
|
||||||
|
'Could not cancel the previous repair attempt, so no new link was generated. Please try again later.',
|
||||||
'暂时无法查询修复状态,请稍后重试。':
|
'暂时无法查询修复状态,请稍后重试。':
|
||||||
'Could not check the repair status right now. Please try again later.',
|
'Could not check the repair status right now. Please try again later.',
|
||||||
'修复流程暂时失败,现有机器人连接不受影响;请稍后发送 /repair 重试。':
|
'修复流程暂时失败,现有机器人连接不受影响;请稍后发送 /repair 重试。':
|
||||||
|
|
@ -69,10 +69,11 @@ export default {
|
||||||
'The repair status query was interrupted; the existing bot connection is unaffected. Send /repair status to retry.',
|
'The repair status query was interrupted; the existing bot connection is unaffected. Send /repair status to retry.',
|
||||||
'链接为短期有效': 'The link is valid for a short time.',
|
'链接为短期有效': 'The link is valid for a short time.',
|
||||||
'链接约 {minutes} 分钟后过期': 'The link expires in about {minutes} minutes',
|
'链接约 {minutes} 分钟后过期': 'The link expires in about {minutes} minutes',
|
||||||
'已有一个修复任务在等待授权。': 'A repair task is already waiting for authorization.',
|
'旧授权链接已作废,已生成新的修复链接。':
|
||||||
'🔧 准备修复卡片按钮。': '🔧 Preparing to repair the card buttons.',
|
'The previous authorization link was invalidated and a new repair link was generated.',
|
||||||
'本次只会增量添加 card.action.trigger。请核对确认页只显示这一项;若出现其他权限或事件,请取消。':
|
'🔧 准备补全权限与回调。': '🔧 Preparing to complete permissions and the callback.',
|
||||||
'Only card.action.trigger will be added. Check that the confirmation page shows only this item; cancel if any other permission or event appears.',
|
'本次最多增量添加三项:卡片回调 card.action.trigger;飞书显示为“获取单聊、群组消息”的租户权限 im:message:readonly(用于读取用户消息中的图片或文件);以及 im:resource(用于上传机器人发送的图片或文件)。确认页只会显示当前缺少的项;若出现上述范围之外的配置,请取消。':
|
||||||
|
'This may incrementally add up to three items: the card callback card.action.trigger; the tenant scope im:message:readonly, shown by Feishu as “Read direct and group messages” and used to read images or files in user messages; and im:resource, used to upload images or files sent by the bot. The confirmation page shows only items the app is currently missing; cancel if anything outside this scope appears.',
|
||||||
'当前设备直接打开:': 'Open directly on this device:',
|
'当前设备直接打开:': 'Open directly on this device:',
|
||||||
'若要用另一台设备扫码,发送 /repair qr。{expiry}。':
|
'若要用另一台设备扫码,发送 /repair qr。{expiry}。':
|
||||||
'To scan with another device, send /repair qr. {expiry}.',
|
'To scan with another device, send /repair qr. {expiry}.',
|
||||||
|
|
@ -199,8 +200,8 @@ export default {
|
||||||
'切换归档显示': 'Toggle archived sessions',
|
'切换归档显示': 'Toggle archived sessions',
|
||||||
'📊 状态': '📊 Status',
|
'📊 状态': '📊 Status',
|
||||||
'📖 帮助': '📖 Help',
|
'📖 帮助': '📖 Help',
|
||||||
'**数字兜底**\n**1**工作区列表 · **2**新会话 · **3**会话列表 · **4**状态\n**5**🔧修复 · **6**帮助':
|
'**数字兜底**\n**1**工作区列表 · **2**新会话 · **3**会话列表 · **4**状态\n**5**🔧补全权限 · **6**帮助':
|
||||||
'**Number fallback**\n**1** Workspace list · **2** New session · **3** Session list · **4** Status\n**5** 🔧 Repair · **6** Help',
|
'**Number fallback**\n**1** Workspace list · **2** New session · **3** Session list · **4** Status\n**5** 🔧 Complete permissions · **6** Help',
|
||||||
'跟随 Host 默认{default}': 'Follow Host default{default}',
|
'跟随 Host 默认{default}': 'Follow Host default{default}',
|
||||||
'**当前**:{value}': '**Current**: {value}',
|
'**当前**:{value}': '**Current**: {value}',
|
||||||
'**Host 默认**:{value}': '**Host default**: {value}',
|
'**Host 默认**:{value}': '**Host default**: {value}',
|
||||||
|
|
@ -241,10 +242,10 @@ export default {
|
||||||
'/steer 指令 给 Agent 补充指令': '/steer INSTRUCTION Steer the Agent',
|
'/steer 指令 给 Agent 补充指令': '/steer INSTRUCTION Steer the Agent',
|
||||||
'**📋 卡片功能**\n\n1. 会话下拉 — 切换当前绑定会话\n2. 工作区下拉 — 切换工作区\n3. 🤖 预设下拉 — 切换 Agent 预设\n4. 🧠 模型下拉 — 切换模型\n5. 🆕 新会话 — 开启全新会话\n6. 📋 会话/关注 — 查看/绑定会话,管理关注\n7. ⏹ 停止 — 停止当前任务\n8. 📐 压缩 — 压缩当前会话上下文\n9. 补充指令 — 给 Agent 发送指令\n10. 🗄 归档切换 — 显示/隐藏归档会话\n11. 📊 状态 — 查看系统连接状态\n12. 📖 帮助 — 查看本帮助':
|
'**📋 卡片功能**\n\n1. 会话下拉 — 切换当前绑定会话\n2. 工作区下拉 — 切换工作区\n3. 🤖 预设下拉 — 切换 Agent 预设\n4. 🧠 模型下拉 — 切换模型\n5. 🆕 新会话 — 开启全新会话\n6. 📋 会话/关注 — 查看/绑定会话,管理关注\n7. ⏹ 停止 — 停止当前任务\n8. 📐 压缩 — 压缩当前会话上下文\n9. 补充指令 — 给 Agent 发送指令\n10. 🗄 归档切换 — 显示/隐藏归档会话\n11. 📊 状态 — 查看系统连接状态\n12. 📖 帮助 — 查看本帮助':
|
||||||
'**📋 Card features**\n\n1. Session dropdown — switch the bound session\n2. Workspace dropdown — switch workspace\n3. 🤖 Preset dropdown — switch Agent Preset\n4. 🧠 Model dropdown — switch model\n5. 🆕 New session — start fresh\n6. 📋 Sessions/watches — view or bind sessions and manage watches\n7. ⏹ Stop — stop the current task\n8. 📐 Compact — compact the current session context\n9. Steer task — send an instruction to the Agent\n10. 🗄 Archived toggle — show or hide archived sessions\n11. 📊 Status — view connection status\n12. 📖 Help — view this help',
|
'**📋 Card features**\n\n1. Session dropdown — switch the bound session\n2. Workspace dropdown — switch workspace\n3. 🤖 Preset dropdown — switch Agent Preset\n4. 🧠 Model dropdown — switch model\n5. 🆕 New session — start fresh\n6. 📋 Sessions/watches — view or bind sessions and manage watches\n7. ⏹ Stop — stop the current task\n8. 📐 Compact — compact the current session context\n9. Steer task — send an instruction to the Agent\n10. 🗄 Archived toggle — show or hide archived sessions\n11. 📊 Status — view connection status\n12. 📖 Help — view this help',
|
||||||
'**⌨️ 文本命令**\n\n`/m` — 打开菜单卡片\n`/new` — 开启全新会话\n`/session ID` — 绑定已有会话\n`/sessionlist [工作区]` — 列出会话\n`/workspace 路径` — 切换工作区\n`/workspacelist` — 列出工作区\n`/status` — 查看连接状态\n`/compact` — 压缩上下文\n`/stop` — 停止当前任务\n`/steer 指令` — 补充指令\n`/watch ID` — 关注会话\n`/watchlist` — 关注列表\n`/unwatch ID` — 取消关注\n`/archived on/off` — 归档显隐\n`/presetlist` — 列出预设\n`/preset [序号/ID]` — 切换预设\n`/preset --default` — 跟随默认\n`/models` — 列出模型\n`/reasoninglist` 或 `/reasonings` — 按序号列出当前模型可用推理等级\n`/reasoning [序号、等级ID或 --default]` — 查看或切换当前推理等级\n`/model [序号或完整模型ID] [推理等级ID]` — 查看或切换当前会话模型\n`/batch` — 开启批量输入(仅私聊,最多 10 条文字)\n`/send` — 提交当前批次\n`/cancel` — 取消当前批次\n`/repair` — 修复卡片按钮':
|
'**⌨️ 文本命令**\n\n`/m` — 打开菜单卡片\n`/new` — 开启全新会话\n`/session ID` — 绑定已有会话\n`/sessionlist [工作区]` — 列出会话\n`/workspace 路径` — 切换工作区\n`/workspacelist` — 列出工作区\n`/status` — 查看连接状态\n`/compact` — 压缩上下文\n`/stop` — 停止当前任务\n`/steer 指令` — 补充指令\n`/watch ID` — 关注会话\n`/watchlist` — 关注列表\n`/unwatch ID` — 取消关注\n`/archived on/off` — 归档显隐\n`/presetlist` — 列出预设\n`/preset [序号/ID]` — 切换预设\n`/preset --default` — 跟随默认\n`/models` — 列出模型\n`/reasoninglist` 或 `/reasonings` — 按序号列出当前模型可用推理等级\n`/reasoning [序号、等级ID或 --default]` — 查看或切换当前推理等级\n`/model [序号或完整模型ID] [推理等级ID]` — 查看或切换当前会话模型\n`/batch` — 开启批量输入(仅私聊,最多 10 条文字)\n`/send` — 提交当前批次\n`/cancel` — 取消当前批次\n`/repair` — 补全飞书权限与卡片回调':
|
||||||
'**⌨️ Text commands**\n\n`/m` — open the menu card\n`/new` — start a new session\n`/session ID` — bind an existing session\n`/sessionlist [workspace]` — list sessions\n`/workspace PATH` — switch workspace\n`/workspacelist` — list workspaces\n`/status` — view connection status\n`/compact` — compact context\n`/stop` — stop the current task\n`/steer INSTRUCTION` — steer the task\n`/watch ID` — watch a session\n`/watchlist` — list watched sessions\n`/unwatch ID` — stop watching\n`/archived on/off` — show or hide archived sessions\n`/presetlist` — list presets\n`/preset [index/ID]` — switch preset\n`/preset --default` — follow default\n`/models` — list models\n`/reasoninglist` or `/reasonings` — list reasoning efforts for the current model\n`/reasoning [index, effort ID, or --default]` — show or switch reasoning effort\n`/model [index or full model ID] [reasoning effort ID]` — show or switch the current Session model\n`/batch` — start batch input (direct messages only, up to 10 text messages)\n`/send` — submit the current batch\n`/cancel` — cancel the current batch\n`/repair` — repair card buttons',
|
'**⌨️ Text commands**\n\n`/m` — open the menu card\n`/new` — start a new session\n`/session ID` — bind an existing session\n`/sessionlist [workspace]` — list sessions\n`/workspace PATH` — switch workspace\n`/workspacelist` — list workspaces\n`/status` — view connection status\n`/compact` — compact context\n`/stop` — stop the current task\n`/steer INSTRUCTION` — steer the task\n`/watch ID` — watch a session\n`/watchlist` — list watched sessions\n`/unwatch ID` — stop watching\n`/archived on/off` — show or hide archived sessions\n`/presetlist` — list presets\n`/preset [index/ID]` — switch preset\n`/preset --default` — follow default\n`/models` — list models\n`/reasoninglist` or `/reasonings` — list reasoning efforts for the current model\n`/reasoning [index, effort ID, or --default]` — show or switch reasoning effort\n`/model [index or full model ID] [reasoning effort ID]` — show or switch the current Session model\n`/batch` — start batch input (direct messages only, up to 10 text messages)\n`/send` — submit the current batch\n`/cancel` — cancel the current batch\n`/repair` — complete Feishu permissions and the card callback',
|
||||||
'**💡 数字兜底**\n回复数字快速操作:\n**1**工作区列表 · **2**新会话 · **3**会话/关注\n**4**状态 · **5**修复 · **6**帮助':
|
'**💡 数字兜底**\n回复数字快速操作:\n**1**工作区列表 · **2**新会话 · **3**会话/关注\n**4**状态 · **5**补全权限 · **6**帮助':
|
||||||
'**💡 Number fallback**\nReply with a number for a quick action:\n**1** Workspace list · **2** New session · **3** Sessions/watches\n**4** Status · **5** Repair · **6** Help',
|
'**💡 Number fallback**\nReply with a number for a quick action:\n**1** Workspace list · **2** New session · **3** Sessions/watches\n**4** Status · **5** Complete permissions · **6** Help',
|
||||||
'从下方下拉选择补充指令;最后一项可自定义输入。':
|
'从下方下拉选择补充指令;最后一项可自定义输入。':
|
||||||
'Choose an instruction below; the last option lets you enter a custom one.',
|
'Choose an instruction below; the last option lets you enter a custom one.',
|
||||||
'当前没有绑定会话,请先绑定会话再补充指令。':
|
'当前没有绑定会话,请先绑定会话再补充指令。':
|
||||||
|
|
@ -262,8 +263,8 @@ export default {
|
||||||
'3 · 新会话': '3 · New session',
|
'3 · 新会话': '3 · New session',
|
||||||
'4 · 状态': '4 · Status',
|
'4 · 状态': '4 · Status',
|
||||||
'5 · 帮助': '5 · Help',
|
'5 · 帮助': '5 · Help',
|
||||||
'**6 · 修复卡片按钮**(请直接回复数字 **6**)':
|
'**6 · 补全权限**(请直接回复数字 **6**)':
|
||||||
'**6 · Repair card buttons** (reply with the number **6**)',
|
'**6 · Complete permissions** (reply with the number **6**)',
|
||||||
'7 · 关注列表': '7 · Watch list',
|
'7 · 关注列表': '7 · Watch list',
|
||||||
'🧪 验证卡片按钮': '🧪 Verify card buttons',
|
'🧪 验证卡片按钮': '🧪 Verify card buttons',
|
||||||
'授权已提交。请点击下方按钮;机器人真实收到回调后才会判定修复成功。':
|
'授权已提交。请点击下方按钮;机器人真实收到回调后才会判定修复成功。':
|
||||||
|
|
@ -290,8 +291,8 @@ export default {
|
||||||
'3 · /new 开启新会话': '3 · /new Start a new session',
|
'3 · /new 开启新会话': '3 · /new Start a new session',
|
||||||
'4 · /status 连接状态': '4 · /status Connection status',
|
'4 · /status 连接状态': '4 · /status Connection status',
|
||||||
'5 · /help 本帮助': '5 · /help This help',
|
'5 · /help 本帮助': '5 · /help This help',
|
||||||
'6 · /repair 修复卡片按钮(请回复数字 6)':
|
'6 · /repair 补全权限与回调(请回复数字 6)':
|
||||||
'6 · /repair Repair card buttons (reply with the number 6)',
|
'6 · /repair Complete permissions and callback (reply with the number 6)',
|
||||||
'7 · /watchlist 关注列表': '7 · /watchlist Watch list',
|
'7 · /watchlist 关注列表': '7 · /watchlist Watch list',
|
||||||
'直接发送文字/图片即继续当前会话。':
|
'直接发送文字/图片即继续当前会话。':
|
||||||
'Send text or an image directly to continue the current session.',
|
'Send text or an image directly to continue the current session.',
|
||||||
|
|
@ -336,8 +337,8 @@ export default {
|
||||||
'飞书机器人': 'Feishu bot',
|
'飞书机器人': 'Feishu bot',
|
||||||
|
|
||||||
// feishu/message-utils.mjs
|
// feishu/message-utils.mjs
|
||||||
'飞书机器人缺少图片读取权限。请在飞书开放平台为该应用添加 im:message:readonly,发布新版本并完成必要的管理员审批后,再重新发送图片。':
|
'飞书机器人缺少图片读取权限 im:message:readonly(飞书显示为“获取单聊、群组消息”)。请私聊机器人执行 /repair 命令,或者在插件页面点击“补全权限”按钮并扫码。按飞书提示发布新版本、完成必要审批后,再重新发送图片。':
|
||||||
'The Feishu bot is missing image-read permission. Add im:message:readonly to the app in Feishu Open Platform, publish a new version, complete the required admin approval, then resend the image.',
|
'The Feishu bot is missing the image-read scope im:message:readonly, shown by Feishu as “Read direct and group messages.” Run /repair in a direct chat with the bot, or click the “Complete permissions” button on the plugin page and scan the QR code. Publish a new version and complete any approval requested by Feishu, then resend the image.',
|
||||||
|
|
||||||
// feishu/feishu-runtime.mjs — callback probe notices
|
// feishu/feishu-runtime.mjs — callback probe notices
|
||||||
'✅ 修复完成:已实测收到 card.action.trigger,菜单按钮现在可用。':
|
'✅ 修复完成:已实测收到 card.action.trigger,菜单按钮现在可用。':
|
||||||
|
|
|
||||||
|
|
@ -980,7 +980,10 @@ test('bridge tells users to grant im:message:readonly when Feishu rejects image
|
||||||
|
|
||||||
assert.equal(sent.length, 1);
|
assert.equal(sent.length, 1);
|
||||||
assert.match(sent[0], /im:message:readonly/);
|
assert.match(sent[0], /im:message:readonly/);
|
||||||
|
assert.match(sent[0], /\/repair/);
|
||||||
assert.match(sent[0], /发布新版本/);
|
assert.match(sent[0], /发布新版本/);
|
||||||
|
assert.match(sent[0], /插件页面/);
|
||||||
|
assert.match(sent[0], /补全权限/);
|
||||||
assert.doesNotMatch(sent[0], /99991672|HTTP 400|secret-shaped|private\/path/);
|
assert.doesNotMatch(sent[0], /99991672|HTTP 400|secret-shaped|private\/path/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -3682,15 +3685,20 @@ function repairCapability({
|
||||||
return {
|
return {
|
||||||
calls,
|
calls,
|
||||||
capability: {
|
capability: {
|
||||||
async start(args) { calls.start.push(args); return startStatus; },
|
async start(args) {
|
||||||
|
calls.start.push(args);
|
||||||
|
return typeof startStatus === 'function'
|
||||||
|
? startStatus(calls.start.length, args)
|
||||||
|
: startStatus;
|
||||||
|
},
|
||||||
async status(args) {
|
async status(args) {
|
||||||
calls.status.push(args);
|
calls.status.push(args);
|
||||||
return typeof status === 'function' ? status(calls.status.length) : status;
|
return typeof status === 'function' ? status(calls.status.length, args) : status;
|
||||||
},
|
},
|
||||||
async cancel(args) {
|
async cancel(args) {
|
||||||
calls.cancel.push(args);
|
calls.cancel.push(args);
|
||||||
return typeof cancelStatus === 'function'
|
return typeof cancelStatus === 'function'
|
||||||
? cancelStatus(calls.cancel.length)
|
? cancelStatus(calls.cancel.length, args)
|
||||||
: cancelStatus;
|
: cancelStatus;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -3699,7 +3707,6 @@ function repairCapability({
|
||||||
|
|
||||||
function repairBridge({
|
function repairBridge({
|
||||||
allowedSenderOpenIds = new Set(['ou_owner']),
|
allowedSenderOpenIds = new Set(['ou_owner']),
|
||||||
repairOwnerOpenIds,
|
|
||||||
capability,
|
capability,
|
||||||
client,
|
client,
|
||||||
sent = [],
|
sent = [],
|
||||||
|
|
@ -3721,7 +3728,6 @@ function repairBridge({
|
||||||
state: fixture.state,
|
state: fixture.state,
|
||||||
status: bridgeStatus(),
|
status: bridgeStatus(),
|
||||||
allowedSenderOpenIds,
|
allowedSenderOpenIds,
|
||||||
repairOwnerOpenIds,
|
|
||||||
botId: REPAIR_BOT_ID,
|
botId: REPAIR_BOT_ID,
|
||||||
appId: REPAIR_APP_ID,
|
appId: REPAIR_APP_ID,
|
||||||
repair: capability,
|
repair: capability,
|
||||||
|
|
@ -3747,10 +3753,64 @@ test('/repair sends a validated ordinary SDK link without prompting Harness', as
|
||||||
assert.equal(fx.asks, 0);
|
assert.equal(fx.asks, 0);
|
||||||
const message = JSON.parse(fx.sent.at(-1).content).text;
|
const message = JSON.parse(fx.sent.at(-1).content).text;
|
||||||
assert.match(message, /card\.action\.trigger/);
|
assert.match(message, /card\.action\.trigger/);
|
||||||
|
assert.match(message, /im:message:readonly/);
|
||||||
|
assert.match(message, /im:resource/);
|
||||||
assert.match(message, new RegExp(REPAIR_URL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
assert.match(message, new RegExp(REPAIR_URL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||||
assert.match(message, /\/repair qr/);
|
assert.match(message, /\/repair qr/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('repeating bare /repair replaces a still-waiting one-time link', async () => {
|
||||||
|
const oldUrl = `${REPAIR_URL}&user_code=old`;
|
||||||
|
const freshUrl = `${REPAIR_URL}&user_code=fresh`;
|
||||||
|
const repair = repairCapability({
|
||||||
|
startStatus: (count) => repairStatus('qr_ready', {
|
||||||
|
attempt: `repair_attempt_${count}`,
|
||||||
|
qrCodeUrl: count === 1 ? oldUrl : freshUrl,
|
||||||
|
}),
|
||||||
|
status: (_count, args) => repairStatus('qr_ready', {
|
||||||
|
attempt: args.attemptId,
|
||||||
|
qrCodeUrl: args.attemptId === 'repair_attempt_1' ? oldUrl : freshUrl,
|
||||||
|
}),
|
||||||
|
cancelStatus: (_count, args) => repairStatus('cancelled', {
|
||||||
|
attempt: args.attemptId,
|
||||||
|
qrCodeUrl: undefined,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const fx = repairBridge({ capability: repair.capability });
|
||||||
|
|
||||||
|
await fx.bridge.accept(event('repair-retry-first', '/repair', { senderOpenId: 'ou_owner' }));
|
||||||
|
await fx.bridge.waitForIdle();
|
||||||
|
await fx.bridge.accept(event('repair-retry-second', '/repair', { senderOpenId: 'ou_owner' }));
|
||||||
|
await fx.bridge.waitForIdle();
|
||||||
|
|
||||||
|
assert.equal(repair.calls.start.length, 2);
|
||||||
|
assert.equal(repair.calls.cancel.length, 1);
|
||||||
|
assert.equal(repair.calls.cancel[0].attemptId, 'repair_attempt_1');
|
||||||
|
const reply = JSON.parse(fx.sent.at(-1).content).text;
|
||||||
|
assert.match(reply, /旧授权链接已作废/);
|
||||||
|
assert.match(reply, /user_code=fresh/);
|
||||||
|
assert.doesNotMatch(reply, /user_code=old/);
|
||||||
|
assert.match(reply, /获取单聊、群组消息/);
|
||||||
|
assert.match(reply, /im:resource/);
|
||||||
|
assert.match(reply, /只会显示当前缺少的项/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('repeating bare /repair never duplicates an update already being saved', async () => {
|
||||||
|
const repair = repairCapability({
|
||||||
|
status: repairStatus('saving', { qrCodeUrl: undefined }),
|
||||||
|
});
|
||||||
|
const fx = repairBridge({ capability: repair.capability });
|
||||||
|
|
||||||
|
await fx.bridge.accept(event('repair-saving-first', '/repair', { senderOpenId: 'ou_owner' }));
|
||||||
|
await fx.bridge.waitForIdle();
|
||||||
|
await fx.bridge.accept(event('repair-saving-second', '/repair', { senderOpenId: 'ou_owner' }));
|
||||||
|
await fx.bridge.waitForIdle();
|
||||||
|
|
||||||
|
assert.equal(repair.calls.start.length, 1);
|
||||||
|
assert.equal(repair.calls.cancel.length, 0);
|
||||||
|
assert.match(JSON.parse(fx.sent.at(-1).content).text, /正在等待专用测试按钮/);
|
||||||
|
});
|
||||||
|
|
||||||
test('/repair status after a runtime restart never starts a duplicate authorization', async () => {
|
test('/repair status after a runtime restart never starts a duplicate authorization', async () => {
|
||||||
const repair = repairCapability();
|
const repair = repairCapability();
|
||||||
const fx = repairBridge({ capability: repair.capability });
|
const fx = repairBridge({ capability: repair.capability });
|
||||||
|
|
@ -3768,14 +3828,14 @@ test('/repair status after a runtime restart never starts a duplicate authorizat
|
||||||
assert.match(message, /不会启动新的授权/);
|
assert.match(message, /不会启动新的授权/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('menu repair entry is number-only and reply 5 starts the same repair flow', async () => {
|
test('menu permission-completion entry is number-only and reply 5 starts the same repair flow', async () => {
|
||||||
const repair = repairCapability();
|
const repair = repairCapability();
|
||||||
const fx = repairBridge({ capability: repair.capability });
|
const fx = repairBridge({ capability: repair.capability });
|
||||||
|
|
||||||
await fx.bridge.accept(event('repair-menu-open', '/m', { senderOpenId: 'ou_owner' }));
|
await fx.bridge.accept(event('repair-menu-open', '/m', { senderOpenId: 'ou_owner' }));
|
||||||
await fx.bridge.waitForIdle();
|
await fx.bridge.waitForIdle();
|
||||||
const menu = cards(fx.sent)[0].content;
|
const menu = cards(fx.sent)[0].content;
|
||||||
assert.match(JSON.stringify(menu), /\*\*5\*\*🔧修复/);
|
assert.match(JSON.stringify(menu), /\*\*5\*\*🔧补全权限/);
|
||||||
assert.equal(buttonsFromCard(menu).some((button) => callbackAction(button) === 'repair'), false);
|
assert.equal(buttonsFromCard(menu).some((button) => callbackAction(button) === 'repair'), false);
|
||||||
|
|
||||||
await fx.bridge.accept(event('repair-menu-five', '5', { senderOpenId: 'ou_owner' }));
|
await fx.bridge.accept(event('repair-menu-five', '5', { senderOpenId: 'ou_owner' }));
|
||||||
|
|
@ -3783,9 +3843,11 @@ test('menu repair entry is number-only and reply 5 starts the same repair flow',
|
||||||
assert.equal(repair.calls.start.length, 1);
|
assert.equal(repair.calls.start.length, 1);
|
||||||
assert.equal(fx.asks, 0);
|
assert.equal(fx.asks, 0);
|
||||||
assert.match(JSON.parse(fx.sent.at(-1).content).text, /card\.action\.trigger/);
|
assert.match(JSON.parse(fx.sent.at(-1).content).text, /card\.action\.trigger/);
|
||||||
|
assert.match(JSON.parse(fx.sent.at(-1).content).text, /im:message:readonly/);
|
||||||
|
assert.match(JSON.parse(fx.sent.at(-1).content).text, /im:resource/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('chat repair requires a private chat and an exact owner; wildcard never authorizes it', async () => {
|
test('chat repair follows the channel access policy without a separate administrator role', async () => {
|
||||||
const wildcardRepair = repairCapability();
|
const wildcardRepair = repairCapability();
|
||||||
const wildcard = repairBridge({
|
const wildcard = repairBridge({
|
||||||
allowedSenderOpenIds: new Set(['*']),
|
allowedSenderOpenIds: new Set(['*']),
|
||||||
|
|
@ -3793,21 +3855,8 @@ test('chat repair requires a private chat and an exact owner; wildcard never aut
|
||||||
});
|
});
|
||||||
await wildcard.bridge.accept(event('repair-wildcard', '/repair', { senderOpenId: 'ou_anyone' }));
|
await wildcard.bridge.accept(event('repair-wildcard', '/repair', { senderOpenId: 'ou_anyone' }));
|
||||||
await wildcard.bridge.waitForIdle();
|
await wildcard.bridge.waitForIdle();
|
||||||
assert.equal(wildcardRepair.calls.start.length, 0);
|
assert.equal(wildcardRepair.calls.start.length, 1);
|
||||||
assert.match(JSON.parse(wildcard.sent.at(-1).content).text, /没有可验证的接入者身份/);
|
assert.equal(wildcardRepair.calls.start[0].actorOpenId, 'ou_anyone');
|
||||||
|
|
||||||
const mixedRepair = repairCapability();
|
|
||||||
const mixed = repairBridge({
|
|
||||||
allowedSenderOpenIds: new Set(['*', 'ou_owner']),
|
|
||||||
capability: mixedRepair.capability,
|
|
||||||
});
|
|
||||||
await mixed.bridge.accept(event('repair-mixed-intruder', '/repair', { senderOpenId: 'ou_other' }));
|
|
||||||
await mixed.bridge.waitForIdle();
|
|
||||||
assert.equal(mixedRepair.calls.start.length, 0);
|
|
||||||
assert.match(JSON.parse(mixed.sent.at(-1).content).text, /只能由机器人接入者/);
|
|
||||||
await mixed.bridge.accept(event('repair-mixed-owner', '/repair', { senderOpenId: 'ou_owner' }));
|
|
||||||
await mixed.bridge.waitForIdle();
|
|
||||||
assert.equal(mixedRepair.calls.start.length, 1);
|
|
||||||
|
|
||||||
const groupRepair = repairCapability();
|
const groupRepair = repairCapability();
|
||||||
const group = repairBridge({ capability: groupRepair.capability });
|
const group = repairBridge({ capability: groupRepair.capability });
|
||||||
|
|
@ -3821,7 +3870,7 @@ test('chat repair requires a private chat and an exact owner; wildcard never aut
|
||||||
assert.match(JSON.parse(group.sent.at(-1).content).text, /请私聊机器人/);
|
assert.match(JSON.parse(group.sent.at(-1).content).text, /请私聊机器人/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('/repair qr, status, verify and cancel stay scoped to the initiating owner', async () => {
|
test('/repair qr, status, verify and cancel stay scoped to the initiating user', async () => {
|
||||||
const sent = [];
|
const sent = [];
|
||||||
let sequence = 0;
|
let sequence = 0;
|
||||||
const client = {
|
const client = {
|
||||||
|
|
@ -3851,6 +3900,8 @@ test('/repair qr, status, verify and cancel stay scoped to the initiating owner'
|
||||||
await fx.bridge.accept(event('repair-commands-status', '/repair status', { senderOpenId: 'ou_owner' }));
|
await fx.bridge.accept(event('repair-commands-status', '/repair status', { senderOpenId: 'ou_owner' }));
|
||||||
await fx.bridge.accept(event('repair-commands-verify', '/repair verify', { senderOpenId: 'ou_owner' }));
|
await fx.bridge.accept(event('repair-commands-verify', '/repair verify', { senderOpenId: 'ou_owner' }));
|
||||||
await fx.bridge.waitForIdle();
|
await fx.bridge.waitForIdle();
|
||||||
|
assert.equal(repair.calls.start.length, 1);
|
||||||
|
assert.equal(repair.calls.cancel.length, 0);
|
||||||
const textMessages = sent
|
const textMessages = sent
|
||||||
.filter((request) => request.data.msg_type === 'text')
|
.filter((request) => request.data.msg_type === 'text')
|
||||||
.map((request) => JSON.parse(request.data.content).text);
|
.map((request) => JSON.parse(request.data.content).text);
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,19 @@ test('Feishu connection check requests and displays test-message feedback', asyn
|
||||||
assert.match(footerChildren[0].props.className, /\bbxf-botActions\b/);
|
assert.match(footerChildren[0].props.className, /\bbxf-botActions\b/);
|
||||||
assert.equal(footerChildren[1].props.role, 'status');
|
assert.equal(footerChildren[1].props.role, 'status');
|
||||||
assert.match(footerChildren[1].props.className, /\bdim-cardFeedback\b/);
|
assert.match(footerChildren[1].props.className, /\bdim-cardFeedback\b/);
|
||||||
|
const repairButton = renderer.root.findByProps({
|
||||||
|
'aria-label': '为飞书测试机器人补全权限与回调',
|
||||||
|
});
|
||||||
|
const repairTooltip = renderer.root.findByProps({ className: 'bxf-repairTooltip' });
|
||||||
|
assert.equal(repairTooltip.props.role, 'tooltip');
|
||||||
|
assert.equal(repairButton.props['aria-describedby'], repairTooltip.props.id);
|
||||||
|
assert.match(textOf(repairTooltip), /card\.action\.trigger/);
|
||||||
|
assert.match(textOf(repairTooltip), /im:message:readonly/);
|
||||||
|
assert.match(textOf(repairTooltip), /im:resource/);
|
||||||
await act(async () => renderer.unmount());
|
await act(async () => renderer.unmount());
|
||||||
|
|
||||||
assert.match(markup, /修复卡片按钮/);
|
assert.match(markup, /补全权限/);
|
||||||
assert.match(markup, /aria-label="修复飞书测试机器人的卡片按钮"/);
|
assert.match(markup, /aria-label="为飞书测试机器人补全权限与回调"/);
|
||||||
assert.match(markup, /<select[^>]*aria-label="群聊响应方式"/);
|
assert.match(markup, /<select[^>]*aria-label="群聊响应方式"/);
|
||||||
assert.match(markup, /仅在 @机器人时响应(推荐)/);
|
assert.match(markup, /仅在 @机器人时响应(推荐)/);
|
||||||
assert.match(markup, /响应所有群消息/);
|
assert.match(markup, /响应所有群消息/);
|
||||||
|
|
@ -416,7 +425,7 @@ test('Feishu callback repair keeps a Host-submitted attempt when a stale QR canc
|
||||||
const card = renderer.root.findByProps({ 'data-bot-id': 'bot_target' });
|
const card = renderer.root.findByProps({ 'data-bot-id': 'bot_target' });
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
card.findAllByType('button')
|
card.findAllByType('button')
|
||||||
.find((button) => textOf(button) === '修复卡片按钮').props.onClick();
|
.find((button) => textOf(button) === '补全权限').props.onClick();
|
||||||
await flushMicrotasks();
|
await flushMicrotasks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -428,9 +437,13 @@ test('Feishu callback repair keeps a Host-submitted attempt when a stale QR canc
|
||||||
'https://open.feishu.cn/page/launcher?tp=sdk&clientID=cli_target',
|
'https://open.feishu.cn/page/launcher?tp=sdk&clientID=cli_target',
|
||||||
);
|
);
|
||||||
assert.match(textOf(renderer.toJSON()), /不会创建新应用/);
|
assert.match(textOf(renderer.toJSON()), /不会创建新应用/);
|
||||||
|
assert.match(textOf(renderer.toJSON()), /im:message:readonly/);
|
||||||
|
assert.match(textOf(renderer.toJSON()), /im:resource/);
|
||||||
|
assert.match(textOf(renderer.toJSON()), /获取单聊、群组消息/);
|
||||||
|
assert.match(textOf(renderer.toJSON()), /当前缺少/);
|
||||||
|
|
||||||
const staleCancel = renderer.root.findAllByType('button')
|
const staleCancel = renderer.root.findAllByType('button')
|
||||||
.find((button) => textOf(button) === '取消修复');
|
.find((button) => textOf(button) === '取消补全');
|
||||||
assert.ok(staleCancel);
|
assert.ok(staleCancel);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
staleCancel.props.onClick();
|
staleCancel.props.onClick();
|
||||||
|
|
@ -440,7 +453,7 @@ test('Feishu callback repair keeps a Host-submitted attempt when a stale QR canc
|
||||||
&& payload.attemptId === 'reg_repair'));
|
&& payload.attemptId === 'reg_repair'));
|
||||||
assert.match(textOf(renderer.toJSON()), /此阶段无法取消/);
|
assert.match(textOf(renderer.toJSON()), /此阶段无法取消/);
|
||||||
assert.equal(renderer.root.findAllByType('button').some(
|
assert.equal(renderer.root.findAllByType('button').some(
|
||||||
(button) => textOf(button) === '取消修复',
|
(button) => textOf(button) === '取消补全',
|
||||||
), false);
|
), false);
|
||||||
assert.ok(timeouts.size > 0, 'submitted repair keeps polling after the refused cancel');
|
assert.ok(timeouts.size > 0, 'submitted repair keeps polling after the refused cancel');
|
||||||
await act(async () => { renderer.unmount(); });
|
await act(async () => { renderer.unmount(); });
|
||||||
|
|
@ -526,7 +539,7 @@ test('Feishu callback repair recovers when a Host restart forgets the browser at
|
||||||
});
|
});
|
||||||
const repairButton = () => renderer.root.findByProps({ 'data-bot-id': 'bot_target' })
|
const repairButton = () => renderer.root.findByProps({ 'data-bot-id': 'bot_target' })
|
||||||
.findAllByType('button')
|
.findAllByType('button')
|
||||||
.find((button) => textOf(button) === '修复卡片按钮');
|
.find((button) => textOf(button) === '补全权限');
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
repairButton().props.onClick();
|
repairButton().props.onClick();
|
||||||
|
|
@ -542,7 +555,7 @@ test('Feishu callback repair recovers when a Host restart forgets the browser at
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
renderer.root.findAllByType('button')
|
renderer.root.findAllByType('button')
|
||||||
.find((button) => textOf(button) === '取消修复').props.onClick();
|
.find((button) => textOf(button) === '取消补全').props.onClick();
|
||||||
await flushMicrotasks();
|
await flushMicrotasks();
|
||||||
});
|
});
|
||||||
assert.match(textOf(renderer.toJSON()), /The provisioning attempt is no longer active/);
|
assert.match(textOf(renderer.toJSON()), /The provisioning attempt is no longer active/);
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,9 @@ function forms(value, result = []) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('menu exposes the increased command set and keeps repair number-only', () => {
|
test('menu exposes the increased command set and keeps permission completion number-only', () => {
|
||||||
const card = JSON.parse(menuCard());
|
const card = JSON.parse(menuCard());
|
||||||
assert.match(JSON.stringify(card), /\*\*5\*\*\S*修复/);
|
assert.match(JSON.stringify(card), /\*\*5\*\*\S*补全权限/);
|
||||||
const actions = buttons(card).flatMap((button) => (
|
const actions = buttons(card).flatMap((button) => (
|
||||||
button.behaviors?.map((behavior) => behavior?.value?.action) ?? []
|
button.behaviors?.map((behavior) => behavior?.value?.action) ?? []
|
||||||
));
|
));
|
||||||
|
|
@ -60,7 +60,7 @@ test('menu exposes the increased command set and keeps repair number-only', () =
|
||||||
'presets', 'models', 'new', 'sessions', 'workspaces',
|
'presets', 'models', 'new', 'sessions', 'workspaces',
|
||||||
'stop', 'compact', 'archive_toggle', 'status', 'help',
|
'stop', 'compact', 'archive_toggle', 'status', 'help',
|
||||||
]);
|
]);
|
||||||
// 修复不占位按钮:仅通过数字兜底「5🔧」触发(见 bridge)
|
// 补全权限不占位按钮:仅通过数字兜底「5🔧」触发(见 bridge)
|
||||||
assert.equal(actions.includes('repair'), false);
|
assert.equal(actions.includes('repair'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,9 @@ test('Feishu image loading maps the missing message scope to an actionable error
|
||||||
await assert.rejects(message.images[0].load({ maxBytes: 1024 }), (error) => {
|
await assert.rejects(message.images[0].load({ maxBytes: 1024 }), (error) => {
|
||||||
assert.equal(error.code, 'feishu-image-permission-required');
|
assert.equal(error.code, 'feishu-image-permission-required');
|
||||||
assert.match(error.userMessage, /im:message:readonly/);
|
assert.match(error.userMessage, /im:message:readonly/);
|
||||||
|
assert.match(error.userMessage, /\/repair/);
|
||||||
|
assert.match(error.userMessage, /插件页面/);
|
||||||
|
assert.match(error.userMessage, /补全权限/);
|
||||||
assert.match(error.userMessage, /发布新版本/);
|
assert.match(error.userMessage, /发布新版本/);
|
||||||
assert.equal(error.cause, providerError);
|
assert.equal(error.cause, providerError);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,7 @@ test('callback repair is deduplicated per bot, updates only its secret, and prov
|
||||||
assert.equal(Object.hasOwn(run.options, 'appPreset'), false);
|
assert.equal(Object.hasOwn(run.options, 'appPreset'), false);
|
||||||
assert.deepEqual(run.options.addons, {
|
assert.deepEqual(run.options.addons, {
|
||||||
preset: false,
|
preset: false,
|
||||||
|
scopes: { tenant: ['im:message:readonly', 'im:resource'] },
|
||||||
callbacks: { items: ['card.action.trigger'] },
|
callbacks: { items: ['card.action.trigger'] },
|
||||||
});
|
});
|
||||||
run.options.onQRCodeReady({
|
run.options.onQRCodeReady({
|
||||||
|
|
@ -517,7 +518,7 @@ test('web callback repair accepts wildcard visibility but probes the precise SDK
|
||||||
await fx.controller.close();
|
await fx.controller.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('chat callback repair rejects SDK authorization by a different operator', async () => {
|
test('chat callback repair has no separate administrator role', async () => {
|
||||||
const existing = bot('bot_existing', 'existing');
|
const existing = bot('bot_existing', 'existing');
|
||||||
const fx = fixture({
|
const fx = fixture({
|
||||||
bots: [existing],
|
bots: [existing],
|
||||||
|
|
@ -540,13 +541,18 @@ test('chat callback repair rejects SDK authorization by a different operator', a
|
||||||
user_info: { open_id: 'ou_different_operator', tenant_brand: 'feishu' },
|
user_info: { open_id: 'ou_different_operator', tenant_brand: 'feishu' },
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => fx.controller.registrationStatus(attemptId).registration.state === 'error');
|
await waitFor(() => fx.controller.registrationStatus(attemptId).registration.state === 'succeeded');
|
||||||
const result = fx.controller.registrationStatus(attemptId);
|
const result = fx.controller.registrationStatus(attemptId);
|
||||||
assert.equal(result.registration.error.code, 'repair_owner_mismatch');
|
assert.equal(result.registration.stage, 'verified');
|
||||||
assert.equal(fx.values.get(existing.secretRef), 'stable-secret');
|
assert.equal(fx.values.get(existing.secretRef), 'rotated-secret');
|
||||||
assert.equal(fx.runtimes.get(existing.id).length, 1);
|
const history = fx.runtimes.get(existing.id);
|
||||||
assert.equal(runtime.stops, 0);
|
assert.equal(history.length, 2);
|
||||||
assert.deepEqual(runtime.probes, []);
|
assert.equal(runtime.stops, 1);
|
||||||
|
assert.deepEqual(history[1].probes, [{
|
||||||
|
expectedOperatorOpenId: existing.ownerOpenIds[0],
|
||||||
|
timeoutMs: 50,
|
||||||
|
chatId: 'oc_owner_chat',
|
||||||
|
}]);
|
||||||
await fx.controller.close();
|
await fx.controller.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import {
|
import {
|
||||||
CallbackRepairManager,
|
CallbackRepairManager,
|
||||||
|
FEISHU_MESSAGE_READ_SCOPE,
|
||||||
|
FEISHU_RESOURCE_SCOPE,
|
||||||
assertCallbackRepairUrl,
|
assertCallbackRepairUrl,
|
||||||
} from '../../../src/channels/feishu/repair-manager.mjs';
|
} from '../../../src/channels/feishu/repair-manager.mjs';
|
||||||
|
|
||||||
|
|
@ -15,7 +17,7 @@ async function waitFor(predicate, timeoutMs = 1000) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test('CallbackRepairManager targets one real app with callbacks only', async () => {
|
test('CallbackRepairManager targets one real app with only the callback and media scopes', async () => {
|
||||||
let observed;
|
let observed;
|
||||||
let resolveRegistration;
|
let resolveRegistration;
|
||||||
const accepted = [];
|
const accepted = [];
|
||||||
|
|
@ -37,9 +39,9 @@ test('CallbackRepairManager targets one real app with callbacks only', async ()
|
||||||
assert.equal(Object.hasOwn(observed, 'appPreset'), false);
|
assert.equal(Object.hasOwn(observed, 'appPreset'), false);
|
||||||
assert.deepEqual(observed.addons, {
|
assert.deepEqual(observed.addons, {
|
||||||
preset: false,
|
preset: false,
|
||||||
|
scopes: { tenant: [FEISHU_MESSAGE_READ_SCOPE, FEISHU_RESOURCE_SCOPE] },
|
||||||
callbacks: { items: ['card.action.trigger'] },
|
callbacks: { items: ['card.action.trigger'] },
|
||||||
});
|
});
|
||||||
assert.equal(Object.hasOwn(observed.addons, 'scopes'), false);
|
|
||||||
assert.equal(Object.hasOwn(observed.addons, 'events'), false);
|
assert.equal(Object.hasOwn(observed.addons, 'events'), false);
|
||||||
|
|
||||||
observed.onQRCodeReady({
|
observed.onQRCodeReady({
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,8 @@ test('Feishu bot cards place the application identifier under the bot name', asy
|
||||||
assert.match(markup, /class="bxf-card bxf-botCard dim-botCard"/);
|
assert.match(markup, /class="bxf-card bxf-botCard dim-botCard"/);
|
||||||
assert.match(markup, /class="bxf-healthPill dim-botHealth"/);
|
assert.match(markup, /class="bxf-healthPill dim-botHealth"/);
|
||||||
assert.match(markup, /<button[^>]*aria-label="检查连接今天是牢梁"[^>]*><span>检查连接<\/span><\/button>/);
|
assert.match(markup, /<button[^>]*aria-label="检查连接今天是牢梁"[^>]*><span>检查连接<\/span><\/button>/);
|
||||||
|
assert.match(markup, /class="bxf-repairAction"[^]*role="tooltip"/);
|
||||||
|
assert.match(markup, /card\.action\.trigger[^]*im:message:readonly[^]*im:resource/);
|
||||||
assert.match(markup, /class="bxf-connectedFooter dim-cardFooter"/);
|
assert.match(markup, /class="bxf-connectedFooter dim-cardFooter"/);
|
||||||
assert.doesNotMatch(markup, /dim-cardSummary|长连接运行正常/);
|
assert.doesNotMatch(markup, /dim-cardSummary|长连接运行正常/);
|
||||||
assert.equal((markup.match(/dim-cardAction(?: |")/g) ?? []).length, 3);
|
assert.equal((markup.match(/dim-cardAction(?: |")/g) ?? []).length, 3);
|
||||||
|
|
@ -241,6 +243,7 @@ test('Feishu bot cards place the application identifier under the bot name', asy
|
||||||
assert.match(markup, /class="dim-presetSelect"/);
|
assert.match(markup, /class="dim-presetSelect"/);
|
||||||
assert.doesNotMatch(markup, />应用标识<|>飞书机器人</);
|
assert.doesNotMatch(markup, />应用标识<|>飞书机器人</);
|
||||||
assert.doesNotMatch(styles, /\.bxf-statusGrid|\.bxf-metric/);
|
assert.doesNotMatch(styles, /\.bxf-statusGrid|\.bxf-metric/);
|
||||||
|
assert.match(styles, /\.bxf-repairAction:hover \.bxf-repairTooltip,[^]*\.bxf-repairAction:focus-within \.bxf-repairTooltip \{[^}]*visibility: visible;/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Feishu keeps its heading controls on one row without a plus icon', async () => {
|
test('Feishu keeps its heading controls on one row without a plus icon', async () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue