@keyou007/agent-bot 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +9 -1
- package/README.zh.md +9 -1
- package/dist/cli/FeishuAppConfiguration.js +87 -25
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuAppRegistration.js +12 -9
- package/dist/cli/FeishuAppRegistration.js.map +1 -1
- package/dist/cli/Initializer.js +7 -6
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/LocalControlClient.js +3 -2
- package/dist/cli/LocalControlClient.js.map +1 -1
- package/dist/cli/LocalControlServer.js +2 -1
- package/dist/cli/LocalControlServer.js.map +1 -1
- package/dist/cli/OptionalAuthorizationSkip.js +27 -6
- package/dist/cli/OptionalAuthorizationSkip.js.map +1 -1
- package/dist/cli/ServerStarter.js +12 -3
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/SkillRegistry.js +5 -4
- package/dist/cli/SkillRegistry.js.map +1 -1
- package/dist/cli/VerificationOutput.js +2 -1
- package/dist/cli/VerificationOutput.js.map +1 -1
- package/dist/cli/help.js +48 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/i18n.js +27 -0
- package/dist/cli/i18n.js.map +1 -0
- package/dist/cli/initOptions.js +5 -4
- package/dist/cli/initOptions.js.map +1 -1
- package/dist/cli/packageVersion.js +2 -1
- package/dist/cli/packageVersion.js.map +1 -1
- package/dist/cli/profile.js +4 -3
- package/dist/cli/profile.js.map +1 -1
- package/dist/cli/serverStatus.js +15 -14
- package/dist/cli/serverStatus.js.map +1 -1
- package/dist/cli/taskChatRoute.js +3 -2
- package/dist/cli/taskChatRoute.js.map +1 -1
- package/dist/cli.js +165 -107
- package/dist/cli.js.map +1 -1
- package/dist/commands/CommandRouter.js +49 -3
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +81 -11
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/docs/technical-reference.md +16 -3
- package/docs/technical-reference.zh.md +16 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +7 -7
- package/src/cli/FeishuAppConfiguration.ts +133 -21
- package/src/cli/FeishuAppRegistration.ts +24 -9
- package/src/cli/Initializer.ts +25 -6
- package/src/cli/LocalControlClient.ts +9 -2
- package/src/cli/LocalControlServer.ts +2 -1
- package/src/cli/OptionalAuthorizationSkip.ts +51 -6
- package/src/cli/ServerStarter.ts +20 -3
- package/src/cli/SkillRegistry.ts +17 -4
- package/src/cli/VerificationOutput.ts +4 -1
- package/src/cli/help.ts +50 -1
- package/src/cli/i18n.ts +38 -0
- package/src/cli/initOptions.ts +17 -4
- package/src/cli/packageVersion.ts +5 -1
- package/src/cli/profile.ts +13 -3
- package/src/cli/serverStatus.ts +19 -14
- package/src/cli/taskChatRoute.ts +9 -2
- package/src/cli.ts +290 -103
- package/src/commands/CommandRouter.ts +58 -3
- package/src/proxy/ProxySessionController.ts +147 -21
|
@@ -35,7 +35,7 @@ The default user-data root is `~/.agent-bot`. `AGENT_BOT_HOME` replaces that roo
|
|
|
35
35
|
|
|
36
36
|
The CLI also supports explicit directory-based profiles. Without `--profile`, commands use the main profile and the normal environment-based path rules. `--profile <directory>` pins both `AGENT_BOT_HOME` and `AGENT_BOT_CONFIG` for the command and every spawned supervisor or worker, with the configuration fixed at `<directory>/config.yaml`. It also clears inherited Feishu credential variables before loading the selected profile's `.env`, which prevents a secondary service launched from inside the primary Agent Bot process tree from accidentally reusing the primary bot. It cannot be combined with `--config`. Alternative profiles must be selected explicitly on every command; Agent Bot does not maintain a named-profile registry.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
The CLI reads the system locale through Node.js internationalization support. Locales beginning with `zh` use Chinese interface text; English and every unsupported locale use English. This applies to help, status, progress, prompts, and CLI-owned errors. JSON field names and enum values are not localized. System-generated restart reasons remain Chinese because they are rendered in Chinese Lark status cards; an explicit `--reason` is preserved verbatim. `agent-bot server status` reports the running worker's Lark App ID as `feishuAppId` in JSON; when the server is stopped or predates that health field, the CLI falls back to the selected profile's configured App ID.
|
|
39
39
|
|
|
40
40
|
| Default path | Contents |
|
|
41
41
|
| ------------------------------------ | ---------------------------------- |
|
|
@@ -75,9 +75,17 @@ When complete credentials are absent, initialization uses Feishu one-click regis
|
|
|
75
75
|
|
|
76
76
|
Missing app configuration is handled in two stages:
|
|
77
77
|
|
|
78
|
-
1. Core configuration is requested and polled until it becomes available.
|
|
78
|
+
1. Core configuration is requested and polled until it becomes available. The manually configured all-group-message scope may be explicitly skipped with `Y`.
|
|
79
79
|
2. Remaining optional configuration is requested. The CLI prints its QR code followed by the authorization URL, then immediately polls for up to five minutes. An interactive terminal offers only `Y` to skip optional authorization and continue; otherwise the user completes authorization in the browser while polling continues.
|
|
80
80
|
|
|
81
|
+
Configuration supported by the one-click launcher is encoded in its `addons` manifest. The core `im:message.group_msg` scope is excluded from that manifest because Feishu does not add it through one-click configuration. Instead, the CLI prints a QR code and this app-specific, pre-filtered Developer Console URL:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
https://open.feishu.cn/app/<appId>/auth?q=im%3Amessage.group_msg&op_from=openapi&token_type=tenant
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The user must add the permission, publish the app version, and complete tenant approval when required. Core polling then detects the permission in the published version before initialization continues. In an interactive terminal, entering `Y` skips only this manual permission wait; other missing core scopes and the message event remain blocking. Initialization returns a partial configuration result and warns that ordinary group messages which do not mention the bot are unavailable.
|
|
88
|
+
|
|
81
89
|
Optional polling failures and timeouts return a partial configuration result instead of failing initialization. When stdin is not an interactive terminal, no skip input is available and polling continues until configuration becomes active or times out. Verification URLs and prompts use stderr, so `--json` keeps its final stdout machine-readable.
|
|
82
90
|
|
|
83
91
|
The generated authorization links do not contain the app secret.
|
|
@@ -93,7 +101,7 @@ Core configuration required for basic messaging:
|
|
|
93
101
|
- Bot capability
|
|
94
102
|
- Persistent-connection event delivery
|
|
95
103
|
- `im.message.receive_v1`
|
|
96
|
-
- `im:message.group_msg
|
|
104
|
+
- `im:message.group_msg` for all user messages in groups containing the bot
|
|
97
105
|
- Tenant permission for private messages
|
|
98
106
|
- `im:message:send_as_bot` or a broader equivalent
|
|
99
107
|
- `application:application:self_manage` so initialization can inspect the published version
|
|
@@ -181,6 +189,8 @@ Starting a Feishu thread from a mapped user message, progress card, or final res
|
|
|
181
189
|
|
|
182
190
|
`/forkgroup` has thread-aware source selection. An unbound thread, or a bound thread task with no completed turn of its own, forks directly from the thread's original anchor turn without creating an intermediate thread task. Once the thread task has completed a turn, `/forkgroup` uses its latest locally persisted completed turn. A newer active turn does not block the command and is excluded from the fork point.
|
|
183
191
|
|
|
192
|
+
The new group's welcome message reports the persisted fork settings: model, reasoning effort, and permission type. Permission type is rendered as automatic execution or confirmation before execution.
|
|
193
|
+
|
|
184
194
|
## Turn And Message Behavior
|
|
185
195
|
|
|
186
196
|
- Plain text creates a task when the route has no current task.
|
|
@@ -188,6 +198,7 @@ Starting a Feishu thread from a mapped user message, progress card, or final res
|
|
|
188
198
|
- A steering race at turn completion becomes the next queued request.
|
|
189
199
|
- `/nosteer` always creates a persistent FIFO queue item.
|
|
190
200
|
- Queue card actions can cancel individual pending items.
|
|
201
|
+
- Exact slash command names take priority. Otherwise, a unique command-name prefix or registered compound-command initialism is expanded before argument parsing; ambiguous matches are rejected with their candidate commands. Registered initialisms are `fg` for `forkgroup`, `ng` for `newgroup`, and `ns` for `nosteer`.
|
|
191
202
|
- Unknown slash commands are rejected and never forwarded as Prompts.
|
|
192
203
|
|
|
193
204
|
Each turn owns one progress card. Normal updates are throttled to one every two seconds; critical updates have a 500 ms minimum gap. On completion, Agent Bot updates the progress card to a terminal state before sending a separate final Markdown message.
|
|
@@ -200,6 +211,8 @@ Incoming rich-text images are downloaded into the inbound image cache and passed
|
|
|
200
211
|
|
|
201
212
|
`/sessions` reads Codex tasks through `thread/list` and can discover tasks created by Codex Desktop, CLI, Agent Bot, or another App Server client under the same `CODEX_HOME`.
|
|
202
213
|
|
|
214
|
+
Each task entry exposes `NewGroup` and `ForkGroup` callbacks. The callback payload keeps the selected task ID and source context, while the Lark operator `open_id` is used to invite the user to the new group. `NewGroup` resolves the selected task's project and execution settings; `ForkGroup` resolves its latest available completed turn.
|
|
215
|
+
|
|
203
216
|
Agent Bot keeps a local routing key for Feishu cards and delivery state, but presents the Codex task ID to users. It does not resume, steer, stop, or fork externally running Codex work without an explicit user action.
|
|
204
217
|
|
|
205
218
|
Project behavior:
|
|
@@ -35,7 +35,7 @@ Agent Bot 是基于 Node.js 22+、ESM 和 TypeScript 的应用,主要组件如
|
|
|
35
35
|
|
|
36
36
|
CLI 还支持显式指定目录的 Profile。不使用 `--profile` 时,命令使用主 Profile 以及原有的环境变量路径规则。`--profile <目录>` 会为当前命令及其启动的 Supervisor、Worker 固定 `AGENT_BOT_HOME` 和 `AGENT_BOT_CONFIG`,配置文件固定为 `<目录>/config.yaml`。它还会先清除继承的飞书凭据环境变量,再加载所选 Profile 的 `.env`,避免从主 Agent Bot 进程树中启动辅助实例时误用主机器人的凭据。`--profile` 不能和 `--config` 同时使用。其他 Profile 必须在每次命令中显式选择;Agent Bot 不维护按名称注册的 Profile。
|
|
37
37
|
|
|
38
|
-
CLI
|
|
38
|
+
CLI 通过 Node.js 国际化能力读取系统 Locale。以 `zh` 开头的 Locale 使用中文界面;英文及所有未支持 Locale 使用英文。帮助、状态、进度、交互提示和 CLI 自身错误都遵循此规则;JSON 字段名及枚举值不做本地化。系统生成的重启原因仍使用中文,因为它会显示在中文 Lark 状态卡中;显式传入的 `--reason` 保持原样。`agent-bot server status` 会报告运行中 Worker 的 Lark App ID,JSON 字段名为 `feishuAppId`;服务停止或旧版健康协议尚未提供该字段时,CLI 会回退到当前 Profile 配置的 App ID。
|
|
39
39
|
|
|
40
40
|
| 默认路径 | 内容 |
|
|
41
41
|
| ------------------------------------ | -------------------- |
|
|
@@ -75,9 +75,17 @@ CLI 界面文本不受系统语言影响,统一使用英文。系统生成的
|
|
|
75
75
|
|
|
76
76
|
缺失配置分两阶段处理:
|
|
77
77
|
|
|
78
|
-
1.
|
|
78
|
+
1. 请求核心配置并轮询,直到基础能力生效;需要手动配置的全部群消息权限允许用户输入 `Y` 显式跳过
|
|
79
79
|
2. 请求其余可选配置。CLI 会先显示二维码,随后显示授权链接,然后立即开始最多 5 分钟的轮询。交互式终端只提供 `Y` 选项,用于跳过可选授权并继续;否则用户直接在浏览器中完成授权,轮询会持续进行
|
|
80
80
|
|
|
81
|
+
一键配置支持的内容会编码到 launcher 的 `addons` 清单中。核心权限 `im:message.group_msg` 不会放入该清单,因为飞书无法通过一键配置新增它。CLI 会改为显示二维码,以及当前应用已筛选该权限的开发者后台直达链接:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
https://open.feishu.cn/app/<appId>/auth?q=im%3Amessage.group_msg&op_from=openapi&token_type=tenant
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
用户需要手动新增权限、发布应用版本,并在需要时完成租户管理员审批。核心轮询会在已发布版本中检测到该权限后继续初始化。交互式终端中输入 `Y` 只会跳过这个手动权限的等待,其他缺失的核心权限和消息事件仍会阻塞。初始化会返回部分配置结果,并提示机器人无法响应未 @ 它的普通群消息。
|
|
88
|
+
|
|
81
89
|
可选配置轮询失败或超时会返回部分配置结果,不会导致初始化失败。stdin 不是交互式终端时无法输入跳过指令,轮询会持续到配置生效或超时。验证链接和提示写入 stderr,因此 `--json` 的最终 stdout 仍可直接供程序读取。
|
|
82
90
|
|
|
83
91
|
生成的授权链接不包含 App Secret。
|
|
@@ -93,7 +101,7 @@ CLI 界面文本不受系统语言影响,统一使用英文。系统生成的
|
|
|
93
101
|
- 机器人能力
|
|
94
102
|
- 长连接事件接收
|
|
95
103
|
- `im.message.receive_v1`
|
|
96
|
-
- `im:message.group_msg
|
|
104
|
+
- `im:message.group_msg`,用于接收机器人所在群内的全部用户消息
|
|
97
105
|
- 接收私聊消息所需的租户权限
|
|
98
106
|
- `im:message:send_as_bot` 或覆盖它的更大权限
|
|
99
107
|
- `application:application:self_manage`,供初始化检查已发布版本
|
|
@@ -181,6 +189,8 @@ AGENT_BOT=1
|
|
|
181
189
|
|
|
182
190
|
`/forkgroup` 会根据话题状态选择来源。话题尚未绑定任务,或者已绑定任务但尚未完成过自己的 turn 时,直接从话题原始锚点 turn fork,不创建中间话题任务。话题任务完成过 turn 后,使用本地持久化的最近完成 turn;更晚的执行中 turn 不会阻塞命令,也不会成为 fork 点。
|
|
183
191
|
|
|
192
|
+
新群欢迎消息会显示分支任务持久化后的模型、思考强度和权限类型;权限类型显示为自动执行或执行前确认。
|
|
193
|
+
|
|
184
194
|
## Turn 与消息行为
|
|
185
195
|
|
|
186
196
|
- 路由没有当前任务时,普通文本会自动创建任务
|
|
@@ -188,6 +198,7 @@ AGENT_BOT=1
|
|
|
188
198
|
- Steering 与 turn 完成发生竞态时,文字变为下一条排队请求
|
|
189
199
|
- `/nosteer` 始终创建持久化 FIFO 队列项
|
|
190
200
|
- 队列卡操作可以取消单个待执行项
|
|
201
|
+
- 完整斜杠命令名优先;否则在解析参数前展开唯一的命令名前缀或已登记的复合命令首字母缩写,匹配多个命令时会拒绝并列出候选命令。当前缩写为 `fg` → `forkgroup`、`ng` → `newgroup`、`ns` → `nosteer`
|
|
191
202
|
- 未知斜杠命令会被拒绝,不会作为 Prompt 转发
|
|
192
203
|
|
|
193
204
|
每个 turn 只有一张进度卡。普通更新最多每两秒一次,关键更新最短间隔 500 毫秒。完成时先把进度卡更新为终态,再单独发送 Markdown 最终回答。
|
|
@@ -200,6 +211,8 @@ AGENT_BOT=1
|
|
|
200
211
|
|
|
201
212
|
`/sessions` 通过 `thread/list` 读取 Codex 任务,可发现同一 `CODEX_HOME` 下由 Codex Desktop、CLI、Agent Bot 或其他 App Server 客户端创建的任务。
|
|
202
213
|
|
|
214
|
+
每个任务条目都提供 `NewGroup` 和 `ForkGroup` 回调。回调数据保留所选任务 ID 与来源上下文,并使用飞书操作者的 `open_id` 邀请用户进入新群。`NewGroup` 解析所选任务的项目和执行设置;`ForkGroup` 解析该任务最新可用的已完成 turn。
|
|
215
|
+
|
|
203
216
|
Agent Bot 内部保留本地路由键以关联飞书卡片和投递状态,但对用户展示 Codex 任务 ID。没有明确用户操作时,不会续写、steer、停止或分支其他客户端正在运行的 Codex 工作。
|
|
204
217
|
|
|
205
218
|
项目规则:
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyou007/agent-bot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@keyou007/agent-bot",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.10",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@agentclientprotocol/sdk": "1.2.1",
|
package/package.json
CHANGED
|
@@ -46,15 +46,15 @@ Run initialization once after installing or linking the CLI:
|
|
|
46
46
|
agent-bot init
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Initialization creates the Agent Bot home, `config.yaml`, `.env`, `data/`, and `logs/` from the bundled examples. If Feishu credentials are absent, it starts Feishu one-click app registration, prints a QR code followed by its authorization URL, waits for the user to approve the request, and stores the returned app ID and secret in `.env`. It then audits the app's currently published tenant permissions, event subscriptions, and callbacks. Core and optional missing items produce separate
|
|
49
|
+
Initialization creates the Agent Bot home, `config.yaml`, `.env`, `data/`, and `logs/` from the bundled examples. If Feishu credentials are absent, it starts Feishu one-click app registration, prints a QR code followed by its authorization URL, waits for the user to approve the request, and stores the returned app ID and secret in `.env`. It then audits the app's currently published tenant permissions, event subscriptions, and callbacks. Core and optional missing items produce separate configuration challenges. Core configuration is polled until ready, except that the manually configured all-group-message permission may be explicitly skipped with `Y`. For optional configuration, the CLI prints the QR code followed by the authorization URL and immediately waits up to five minutes for activation. The only terminal choice is also `Y`, which skips optional authorization and continues initialization.
|
|
50
50
|
|
|
51
|
-
Core messaging configuration includes `im:message.group_msg
|
|
51
|
+
Core messaging configuration includes `im:message.group_msg`, not merely the narrower group-mention scope. Accept `im:message.group_msg:readonly` when an existing app version reports that alias. Feishu cannot add `im:message.group_msg` through the one-click launcher, so Agent Bot excludes it from `addons` and prints a QR code plus a direct filtered Developer Console permission-page URL. The user should add the permission manually, publish the app version, and complete tenant approval if required. Entering `Y` skips only this permission wait and continues with a partial configuration; the bot then cannot respond to ordinary group messages that do not mention it.
|
|
52
52
|
|
|
53
53
|
Initialization always requests all-user group-message delivery. The runtime option `feishu.respondToAllGroupMessages` controls whether Agent Bot uses it: the default `true` responds to ordinary group messages, while `false` requires a mention of the current bot. Private messages are unaffected.
|
|
54
54
|
|
|
55
55
|
After Feishu initialization succeeds, `init` releases its initialization lock and automatically starts the selected profile's server, waiting until it is ready. It does not create another supervisor when that profile is already running. `init --skip-feishu` is the exception: it prepares Console-only files and does not start the server. With `--json`, inspect `server.status` for `started`, `already-running`, or `skipped`.
|
|
56
56
|
|
|
57
|
-
When running `init` on the user's behalf, relay every exact authorization
|
|
57
|
+
When running `init` on the user's behalf, relay every exact authorization, incremental-configuration, and manual permission-page URL and let the user complete it in their own browser. For `im:message.group_msg`, explicitly tell the user to add the filtered permission and publish the app version, or offer to enter `Y` when they intentionally accept mention-only group behavior. Optional polling starts automatically after the link is shown. Do not enter `Y` for either prompt unless the user explicitly asks to skip; there is no separate accept action. Optional capabilities such as group creation, group-title synchronization, reactions, images, and card actions never block initialization when skipped, timed out, or still unavailable; relay the final affected-feature warnings. In a non-interactive terminal, skip input is unavailable. None of the URLs contains the app secret, and the secret itself is never printed.
|
|
58
58
|
|
|
59
59
|
Existing complete Feishu credentials are preserved but still audited and repaired when needed. Missing or incomplete credentials mean app creation did not complete, so a later `init` starts a new registration instead of resuming the previous device code. Once both credentials are durably saved, an interrupted permission audit resumes against the same remote app. `init` also holds a local lock to prevent concurrent registrations. Use `agent-bot init --skip-feishu` only for console-only initialization because it skips both registration and configuration auditing. Use `agent-bot init --reconfigure-feishu` only when the user explicitly wants to replace existing complete credentials. The command respects `AGENT_BOT_HOME`, `AGENT_BOT_CONFIG`, and `--config <path>`. Use `agent-bot init --json` when structured final output helps.
|
|
60
60
|
|
|
@@ -80,7 +80,7 @@ agent-bot server status
|
|
|
80
80
|
agent-bot task list
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
Agent Bot CLI interface text follows the system locale: locales beginning with `zh` use Chinese, while English and unsupported locales use English. JSON output is not localized. `agent-bot server status` also reports the running profile's Lark App ID; with `--json`, read it from `feishuAppId`.
|
|
84
84
|
|
|
85
85
|
Add `--json` to status and list commands when machine-readable output helps. If `agent-bot` is unavailable, report that the CLI is not installed or linked instead of guessing process state.
|
|
86
86
|
|
|
@@ -139,11 +139,11 @@ Use `/newgroup [title]` in Feishu to create a private group named `[agent] [proj
|
|
|
139
139
|
|
|
140
140
|
Group names created by `/newgroup` and `/forkgroup` are capped at 60 displayed characters. When the generated name would exceed that limit, truncate only the title portion in the Feishu group name; preserve the underlying Agent Bot task title.
|
|
141
141
|
|
|
142
|
-
Use `/forkgroup [title]` in Feishu to fork the current position into a newly created private group. In a Feishu thread, use the thread's original anchor turn when the thread has no bound task or its bound task has not completed a turn of its own. Once the bound thread task has completed a turn, use its latest locally persisted completed turn; exclude any newer active turn without interrupting it. Resolve this source before generic thread initialization so an unbound thread does not create an intermediate task. Outside a thread, fork the current Codex task's latest available completed turn. Agent Bot invites the command sender and binds the fork as the new group's current task, but does not automatically send a Sessions or Status card there. An explicit title is used directly. Without an explicit title, both the forked task and group name use the same persistent `source title(分支 N)` sequence as `/fork`. `/forkgroup` does not add a date suffix.
|
|
142
|
+
Use `/forkgroup [title]` in Feishu to fork the current position into a newly created private group. In a Feishu thread, use the thread's original anchor turn when the thread has no bound task or its bound task has not completed a turn of its own. Once the bound thread task has completed a turn, use its latest locally persisted completed turn; exclude any newer active turn without interrupting it. Resolve this source before generic thread initialization so an unbound thread does not create an intermediate task. Outside a thread, fork the current Codex task's latest available completed turn. Agent Bot invites the command sender and binds the fork as the new group's current task, but does not automatically send a Sessions or Status card there. The new-group welcome message reports the forked task's model, reasoning effort, and permission type. An explicit title is used directly. Without an explicit title, both the forked task and group name use the same persistent `source title(分支 N)` sequence as `/fork`. `/forkgroup` does not add a date suffix.
|
|
143
143
|
|
|
144
144
|
Use `/new [title] --nodir` to force a new Codex Projectless task even when the current task belongs to a project. `--nodir` and `--dir <cwd>` are mutually exclusive; omitting both preserves the normal project-shape inheritance behavior.
|
|
145
145
|
|
|
146
|
-
Every task in the Feishu `/sessions` card has
|
|
146
|
+
Every task in the Feishu `/sessions` card has `New`, `NewGroup`, `Fork`, and `ForkGroup` actions. `New` inherits the source task's project directory, model, reasoning effort, and permission mode, creates a new task, and switches the current chat to it without stopping or forking the source task. `NewGroup` applies the same inheritance in a newly created private group. `Fork` switches the current chat to a branch, while `ForkGroup` binds the branch to a newly created private group; both fork actions use the selected task's latest available completed turn without interrupting an active turn.
|
|
147
147
|
|
|
148
148
|
Use `/fork [number-or-task-id]` in Feishu to branch from the current or specified Codex task and switch to the new branch. If the source task is running, Agent Bot forks from its latest completed turn and leaves the active turn running; it rejects only when the task has no completed turn yet.
|
|
149
149
|
|
|
@@ -153,7 +153,7 @@ Use `/model` in Feishu to show an interactive Card 2.0 model selector for the cu
|
|
|
153
153
|
|
|
154
154
|
Keep action labels placed to the right of card content or at the bottom of a card in English. Buttons above the main card content, such as activity-history navigation, may remain in Chinese.
|
|
155
155
|
|
|
156
|
-
Messages whose trimmed text starts with `/` are always parsed as Agent Bot commands. Unknown slash commands are reported to the user with a `/help` hint and must never fall through to the model, including slash-prefixed messages that also contain images.
|
|
156
|
+
Messages whose trimmed text starts with `/` are always parsed as Agent Bot commands. Command names accept unique prefixes, such as `/sess` for `/sessions`. Compound commands also accept registered initialisms: `/fg` for `/forkgroup`, `/ng` for `/newgroup`, and `/ns` for `/nosteer`. Exact command names take priority; ambiguous matches are rejected and list every matching command. Unknown slash commands are reported to the user with a `/help` hint and must never fall through to the model, including slash-prefixed messages that also contain images.
|
|
157
157
|
|
|
158
158
|
When a group body has a current task, renaming the Feishu group to `[agent-name] [project directory] new title` also renames that current task if the Agent prefix matches the task's configured agent. Only `new title` is written to the task; the Agent and project-directory prefixes remain group metadata. The older `[agent-name] new title` shape remains supported. Malformed names, agent mismatches, empty groups, and thread-specific tasks are ignored.
|
|
159
159
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { gzipSync } from "node:zlib";
|
|
2
2
|
import type { FeishuAppCredentials } from "./FeishuAppRegistration.js";
|
|
3
|
+
import { cliText } from "./i18n.js";
|
|
3
4
|
|
|
4
5
|
const FEISHU_OPEN_BASE_URL = "https://open.feishu.cn";
|
|
5
6
|
const TENANT_TOKEN_PATH = "/open-apis/auth/v3/tenant_access_token/internal";
|
|
@@ -28,10 +29,10 @@ const REQUIRED_SCOPE_REQUIREMENTS: ScopeRequirement[] = [
|
|
|
28
29
|
accepted: ["im:chat:read", "im:chat:readonly", "im:chat"],
|
|
29
30
|
},
|
|
30
31
|
{
|
|
31
|
-
requested: "im:message.group_msg
|
|
32
|
+
requested: "im:message.group_msg",
|
|
32
33
|
accepted: [
|
|
33
|
-
"im:message.group_msg:readonly",
|
|
34
34
|
"im:message.group_msg",
|
|
35
|
+
"im:message.group_msg:readonly",
|
|
35
36
|
],
|
|
36
37
|
},
|
|
37
38
|
{
|
|
@@ -66,11 +67,12 @@ export const REQUIRED_FEISHU_CALLBACKS = ["card.action.trigger"] as const;
|
|
|
66
67
|
|
|
67
68
|
const CORE_FEISHU_SCOPES = new Set([
|
|
68
69
|
"application:application:self_manage",
|
|
69
|
-
"im:message.group_msg
|
|
70
|
+
"im:message.group_msg",
|
|
70
71
|
"im:message.p2p_msg:readonly",
|
|
71
72
|
"im:message:send_as_bot",
|
|
72
73
|
]);
|
|
73
74
|
const CORE_FEISHU_EVENTS = new Set<string>(["im.message.receive_v1"]);
|
|
75
|
+
const MANUAL_FEISHU_SCOPES = new Set(["im:message.group_msg"]);
|
|
74
76
|
|
|
75
77
|
export interface FeishuAppConfiguration {
|
|
76
78
|
scopes: string[];
|
|
@@ -85,6 +87,7 @@ export interface MissingFeishuAppConfiguration {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
export interface FeishuConfigurationChallenge {
|
|
90
|
+
kind: "launcher" | "manual_scope";
|
|
88
91
|
verificationUrl: string;
|
|
89
92
|
missing: MissingFeishuAppConfiguration;
|
|
90
93
|
blocking: boolean;
|
|
@@ -94,6 +97,7 @@ export interface EnsureFeishuAppConfigurationOptions {
|
|
|
94
97
|
fetch?: typeof globalThis.fetch;
|
|
95
98
|
sleep?: (milliseconds: number) => Promise<void>;
|
|
96
99
|
signal?: AbortSignal;
|
|
100
|
+
manualPermissionSkipSignal?: AbortSignal;
|
|
97
101
|
optionalSkipSignal?: AbortSignal;
|
|
98
102
|
pollIntervalMs?: number;
|
|
99
103
|
timeoutMs?: number;
|
|
@@ -157,15 +161,27 @@ export async function ensureFeishuAppConfiguration(
|
|
|
157
161
|
|
|
158
162
|
let remainingMs = timeoutMs;
|
|
159
163
|
let lastError: unknown;
|
|
164
|
+
let refreshImmediately = options.manualPermissionSkipSignal?.aborted === true;
|
|
160
165
|
while (remainingMs > 0) {
|
|
161
166
|
const waitMs = Math.min(pollIntervalMs, remainingMs);
|
|
162
|
-
|
|
163
|
-
|
|
167
|
+
if (!refreshImmediately) {
|
|
168
|
+
const waitResult = options.manualPermissionSkipSignal?.aborted
|
|
169
|
+
? await sleepWithAbort(sleep, waitMs, options.signal).then(() => "elapsed" as const)
|
|
170
|
+
: await sleepWithOptionalSkip(
|
|
171
|
+
sleep,
|
|
172
|
+
waitMs,
|
|
173
|
+
options.signal,
|
|
174
|
+
options.manualPermissionSkipSignal,
|
|
175
|
+
);
|
|
176
|
+
if (waitResult === "elapsed") remainingMs -= waitMs;
|
|
177
|
+
}
|
|
178
|
+
refreshImmediately = false;
|
|
164
179
|
|
|
165
180
|
try {
|
|
166
181
|
configuration = await readFeishuAppConfiguration(credentials, fetchImpl, options.signal);
|
|
167
182
|
const remaining = missingFeishuAppConfiguration(configuration);
|
|
168
|
-
|
|
183
|
+
const unresolvedCore = coreMissingFeishuAppConfiguration(remaining);
|
|
184
|
+
if (!hasBlockingCoreConfiguration(unresolvedCore, options.manualPermissionSkipSignal)) {
|
|
169
185
|
return completeOptionalFeishuConfiguration(
|
|
170
186
|
credentials,
|
|
171
187
|
configuration,
|
|
@@ -175,17 +191,40 @@ export async function ensureFeishuAppConfiguration(
|
|
|
175
191
|
pollIntervalMs,
|
|
176
192
|
optionalTimeoutMs,
|
|
177
193
|
options,
|
|
194
|
+
skippedManualScopes(coreMissing, options.manualPermissionSkipSignal),
|
|
178
195
|
);
|
|
179
196
|
}
|
|
180
197
|
lastError = undefined;
|
|
181
198
|
} catch (error) {
|
|
182
199
|
if (options.signal?.aborted) throw abortError(options.signal);
|
|
200
|
+
const knownRemaining = missingFeishuAppConfiguration(configuration);
|
|
201
|
+
if (!hasBlockingCoreConfiguration(
|
|
202
|
+
coreMissingFeishuAppConfiguration(knownRemaining),
|
|
203
|
+
options.manualPermissionSkipSignal,
|
|
204
|
+
)) {
|
|
205
|
+
return completeOptionalFeishuConfiguration(
|
|
206
|
+
credentials,
|
|
207
|
+
configuration,
|
|
208
|
+
missing,
|
|
209
|
+
fetchImpl,
|
|
210
|
+
sleep,
|
|
211
|
+
pollIntervalMs,
|
|
212
|
+
optionalTimeoutMs,
|
|
213
|
+
options,
|
|
214
|
+
skippedManualScopes(coreMissing, options.manualPermissionSkipSignal),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
183
217
|
lastError = error;
|
|
184
218
|
}
|
|
185
219
|
}
|
|
186
220
|
|
|
187
|
-
const detail = lastError instanceof Error
|
|
188
|
-
|
|
221
|
+
const detail = lastError instanceof Error
|
|
222
|
+
? cliText(` Last check failed: ${lastError.message}`, ` 上次检查失败:${lastError.message}`)
|
|
223
|
+
: "";
|
|
224
|
+
throw new Error(cliText(
|
|
225
|
+
`Timed out waiting for the core Lark scopes and message event. Run agent-bot init again.${detail}`,
|
|
226
|
+
`等待飞书核心权限和消息事件超时。请重新运行 agent-bot init。${detail}`,
|
|
227
|
+
));
|
|
189
228
|
}
|
|
190
229
|
|
|
191
230
|
async function requestMissingFeishuConfiguration(
|
|
@@ -194,8 +233,28 @@ async function requestMissingFeishuConfiguration(
|
|
|
194
233
|
blocking: boolean,
|
|
195
234
|
onVerification: EnsureFeishuAppConfigurationOptions["onVerification"],
|
|
196
235
|
): Promise<void> {
|
|
197
|
-
const
|
|
198
|
-
|
|
236
|
+
const launcherMissing: MissingFeishuAppConfiguration = {
|
|
237
|
+
scopes: missing.scopes.filter((scope) => !MANUAL_FEISHU_SCOPES.has(scope)),
|
|
238
|
+
events: missing.events,
|
|
239
|
+
callbacks: missing.callbacks,
|
|
240
|
+
};
|
|
241
|
+
if (hasMissingConfiguration(launcherMissing)) {
|
|
242
|
+
await onVerification?.({
|
|
243
|
+
kind: "launcher",
|
|
244
|
+
verificationUrl: buildFeishuConfigurationUrl(appId, launcherMissing),
|
|
245
|
+
missing: launcherMissing,
|
|
246
|
+
blocking,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
for (const scope of missing.scopes.filter((value) => MANUAL_FEISHU_SCOPES.has(value))) {
|
|
251
|
+
await onVerification?.({
|
|
252
|
+
kind: "manual_scope",
|
|
253
|
+
verificationUrl: buildFeishuPermissionManagementUrl(appId, scope),
|
|
254
|
+
missing: { scopes: [scope], events: [], callbacks: [] },
|
|
255
|
+
blocking,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
199
258
|
}
|
|
200
259
|
|
|
201
260
|
async function completeOptionalFeishuConfiguration(
|
|
@@ -207,13 +266,15 @@ async function completeOptionalFeishuConfiguration(
|
|
|
207
266
|
pollIntervalMs: number,
|
|
208
267
|
timeoutMs: number,
|
|
209
268
|
options: EnsureFeishuAppConfigurationOptions,
|
|
269
|
+
ignoredScopes: ReadonlySet<string> = new Set(),
|
|
210
270
|
): Promise<EnsureFeishuAppConfigurationResult> {
|
|
211
271
|
let configuration = initialConfiguration;
|
|
212
272
|
let remaining = missingFeishuAppConfiguration(configuration);
|
|
213
|
-
|
|
273
|
+
const requested = withoutIgnoredScopes(remaining, ignoredScopes);
|
|
274
|
+
if (hasMissingConfiguration(requested)) {
|
|
214
275
|
await requestMissingFeishuConfiguration(
|
|
215
276
|
credentials.appId,
|
|
216
|
-
|
|
277
|
+
requested,
|
|
217
278
|
false,
|
|
218
279
|
options.onVerification,
|
|
219
280
|
);
|
|
@@ -227,6 +288,7 @@ async function completeOptionalFeishuConfiguration(
|
|
|
227
288
|
timeoutMs,
|
|
228
289
|
options.signal,
|
|
229
290
|
options.optionalSkipSignal,
|
|
291
|
+
ignoredScopes,
|
|
230
292
|
);
|
|
231
293
|
configuration = completed.configuration;
|
|
232
294
|
remaining = completed.remaining;
|
|
@@ -249,6 +311,7 @@ async function waitForOptionalFeishuConfiguration(
|
|
|
249
311
|
timeoutMs: number,
|
|
250
312
|
signal?: AbortSignal,
|
|
251
313
|
optionalSkipSignal?: AbortSignal,
|
|
314
|
+
ignoredScopes: ReadonlySet<string> = new Set(),
|
|
252
315
|
): Promise<{
|
|
253
316
|
configuration: FeishuAppConfiguration;
|
|
254
317
|
remaining: MissingFeishuAppConfiguration;
|
|
@@ -256,7 +319,7 @@ async function waitForOptionalFeishuConfiguration(
|
|
|
256
319
|
let configuration = initialConfiguration;
|
|
257
320
|
let remaining = missingFeishuAppConfiguration(configuration);
|
|
258
321
|
let remainingMs = timeoutMs;
|
|
259
|
-
while (remainingMs > 0 && hasMissingConfiguration(remaining)) {
|
|
322
|
+
while (remainingMs > 0 && hasMissingConfiguration(withoutIgnoredScopes(remaining, ignoredScopes))) {
|
|
260
323
|
if (optionalSkipSignal?.aborted) break;
|
|
261
324
|
const waitMs = Math.min(pollIntervalMs, remainingMs);
|
|
262
325
|
const waitResult = await sleepWithOptionalSkip(
|
|
@@ -346,6 +409,14 @@ export function buildFeishuConfigurationUrl(appId: string, missing: MissingFeish
|
|
|
346
409
|
return url.toString();
|
|
347
410
|
}
|
|
348
411
|
|
|
412
|
+
export function buildFeishuPermissionManagementUrl(appId: string, scope: string): string {
|
|
413
|
+
const url = new URL(`/app/${encodeURIComponent(appId)}/auth`, FEISHU_OPEN_BASE_URL);
|
|
414
|
+
url.searchParams.set("q", scope);
|
|
415
|
+
url.searchParams.set("op_from", "openapi");
|
|
416
|
+
url.searchParams.set("token_type", "tenant");
|
|
417
|
+
return url.toString();
|
|
418
|
+
}
|
|
419
|
+
|
|
349
420
|
async function readFeishuAppConfiguration(
|
|
350
421
|
credentials: FeishuAppCredentials,
|
|
351
422
|
fetchImpl: typeof globalThis.fetch,
|
|
@@ -360,14 +431,14 @@ async function readFeishuAppConfiguration(
|
|
|
360
431
|
`${FEISHU_OPEN_BASE_URL}${APPLICATION_PATH_PREFIX}/${appId}?lang=zh_cn`,
|
|
361
432
|
{ headers },
|
|
362
433
|
signal,
|
|
363
|
-
"Read Lark app configuration",
|
|
434
|
+
cliText("Read Lark app configuration", "读取飞书应用配置"),
|
|
364
435
|
),
|
|
365
436
|
fetchJson(
|
|
366
437
|
fetchImpl,
|
|
367
438
|
`${FEISHU_OPEN_BASE_URL}${APPLICATION_PATH_PREFIX}/${appId}/app_versions?lang=zh_cn&page_size=2`,
|
|
368
439
|
{ headers },
|
|
369
440
|
signal,
|
|
370
|
-
"Read Lark app versions",
|
|
441
|
+
cliText("Read Lark app versions", "读取飞书应用版本"),
|
|
371
442
|
),
|
|
372
443
|
]);
|
|
373
444
|
|
|
@@ -415,10 +486,13 @@ async function getTenantAccessToken(
|
|
|
415
486
|
}),
|
|
416
487
|
},
|
|
417
488
|
signal,
|
|
418
|
-
"Get the Lark tenant_access_token",
|
|
489
|
+
cliText("Get the Lark tenant_access_token", "获取飞书 tenant_access_token"),
|
|
419
490
|
);
|
|
420
491
|
const token = readString(payload, "tenant_access_token");
|
|
421
|
-
if (!token) throw new Error(
|
|
492
|
+
if (!token) throw new Error(cliText(
|
|
493
|
+
"The Lark tenant_access_token response is missing the access token.",
|
|
494
|
+
"飞书 tenant_access_token 响应中缺少访问令牌。",
|
|
495
|
+
));
|
|
422
496
|
return token;
|
|
423
497
|
}
|
|
424
498
|
|
|
@@ -439,12 +513,18 @@ async function fetchJson(
|
|
|
439
513
|
const response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
440
514
|
const payload = (await response.json().catch(() => undefined)) as unknown;
|
|
441
515
|
if (!isJsonObject(payload)) {
|
|
442
|
-
throw new Error(
|
|
516
|
+
throw new Error(cliText(
|
|
517
|
+
`${operation} returned an invalid response (HTTP ${response.status}).`,
|
|
518
|
+
`${operation}返回了无效响应(HTTP ${response.status})。`,
|
|
519
|
+
));
|
|
443
520
|
}
|
|
444
521
|
const code = readNumber(payload, "code");
|
|
445
522
|
if (!response.ok || code !== 0) {
|
|
446
523
|
throw new FeishuConfigurationApiError(
|
|
447
|
-
|
|
524
|
+
cliText(
|
|
525
|
+
`${operation} failed: ${readString(payload, "msg") || response.statusText || `HTTP ${response.status}`}`,
|
|
526
|
+
`${operation}失败:${readString(payload, "msg") || response.statusText || `HTTP ${response.status}`}`,
|
|
527
|
+
),
|
|
448
528
|
code,
|
|
449
529
|
response.status,
|
|
450
530
|
);
|
|
@@ -452,7 +532,10 @@ async function fetchJson(
|
|
|
452
532
|
return payload;
|
|
453
533
|
} catch (error) {
|
|
454
534
|
if (parentSignal?.aborted) throw abortError(parentSignal);
|
|
455
|
-
if (controller.signal.aborted) throw new Error(
|
|
535
|
+
if (controller.signal.aborted) throw new Error(cliText(
|
|
536
|
+
`${operation} timed out.`,
|
|
537
|
+
`${operation}超时。`,
|
|
538
|
+
));
|
|
456
539
|
throw error;
|
|
457
540
|
} finally {
|
|
458
541
|
clearTimeout(timeout);
|
|
@@ -484,6 +567,33 @@ function hasMissingConfiguration(missing: MissingFeishuAppConfiguration): boolea
|
|
|
484
567
|
return missing.scopes.length > 0 || missing.events.length > 0 || missing.callbacks.length > 0;
|
|
485
568
|
}
|
|
486
569
|
|
|
570
|
+
function hasBlockingCoreConfiguration(
|
|
571
|
+
missing: MissingFeishuAppConfiguration,
|
|
572
|
+
manualPermissionSkipSignal?: AbortSignal,
|
|
573
|
+
): boolean {
|
|
574
|
+
if (!manualPermissionSkipSignal?.aborted) return hasMissingConfiguration(missing);
|
|
575
|
+
return hasMissingConfiguration(withoutIgnoredScopes(missing, MANUAL_FEISHU_SCOPES));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function skippedManualScopes(
|
|
579
|
+
initialCoreMissing: MissingFeishuAppConfiguration,
|
|
580
|
+
manualPermissionSkipSignal?: AbortSignal,
|
|
581
|
+
): ReadonlySet<string> {
|
|
582
|
+
if (!manualPermissionSkipSignal?.aborted) return new Set();
|
|
583
|
+
return new Set(initialCoreMissing.scopes.filter((scope) => MANUAL_FEISHU_SCOPES.has(scope)));
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function withoutIgnoredScopes(
|
|
587
|
+
missing: MissingFeishuAppConfiguration,
|
|
588
|
+
ignoredScopes: ReadonlySet<string>,
|
|
589
|
+
): MissingFeishuAppConfiguration {
|
|
590
|
+
return {
|
|
591
|
+
scopes: missing.scopes.filter((scope) => !ignoredScopes.has(scope)),
|
|
592
|
+
events: missing.events,
|
|
593
|
+
callbacks: missing.callbacks,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
487
597
|
function coreMissingFeishuAppConfiguration(missing: MissingFeishuAppConfiguration): MissingFeishuAppConfiguration {
|
|
488
598
|
return {
|
|
489
599
|
scopes: missing.scopes.filter((scope) => CORE_FEISHU_SCOPES.has(scope)),
|
|
@@ -554,7 +664,9 @@ function positiveInteger(value: number | undefined): number | undefined {
|
|
|
554
664
|
}
|
|
555
665
|
|
|
556
666
|
function abortError(signal: AbortSignal): Error {
|
|
557
|
-
return signal.reason instanceof Error
|
|
667
|
+
return signal.reason instanceof Error
|
|
668
|
+
? signal.reason
|
|
669
|
+
: new Error(cliText("Lark app configuration was cancelled.", "飞书应用配置已取消。"));
|
|
558
670
|
}
|
|
559
671
|
|
|
560
672
|
function delay(milliseconds: number): Promise<void> {
|
|
@@ -41,12 +41,15 @@ export async function registerFeishuApp(options: FeishuAppRegistrationOptions =
|
|
|
41
41
|
},
|
|
42
42
|
options.signal,
|
|
43
43
|
);
|
|
44
|
-
throwRegistrationError(begin, "Could not start Lark app creation");
|
|
44
|
+
throwRegistrationError(begin, cliText("Could not start Lark app creation", "无法开始创建飞书应用"));
|
|
45
45
|
|
|
46
46
|
const deviceCode = readString(begin, "device_code");
|
|
47
47
|
const userCode = readString(begin, "user_code");
|
|
48
48
|
if (!deviceCode || !userCode) {
|
|
49
|
-
throw new Error(
|
|
49
|
+
throw new Error(cliText(
|
|
50
|
+
"The Lark app creation response is missing device_code or user_code.",
|
|
51
|
+
"飞书应用创建响应中缺少 device_code 或 user_code。",
|
|
52
|
+
));
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
const expiresIn =
|
|
@@ -113,16 +116,22 @@ export async function registerFeishuApp(options: FeishuAppRegistrationOptions =
|
|
|
113
116
|
intervalSeconds = Math.min(intervalSeconds + 5, MAX_POLL_INTERVAL_SECONDS);
|
|
114
117
|
continue;
|
|
115
118
|
case "access_denied":
|
|
116
|
-
throw new Error("Lark app creation was cancelled.");
|
|
119
|
+
throw new Error(cliText("Lark app creation was cancelled.", "飞书应用创建已取消。"));
|
|
117
120
|
case "expired_token":
|
|
118
121
|
case "invalid_grant":
|
|
119
|
-
throw new Error(
|
|
122
|
+
throw new Error(cliText(
|
|
123
|
+
"The Lark app creation link expired. Run agent-bot init again.",
|
|
124
|
+
"飞书应用创建链接已过期,请重新运行 agent-bot init。",
|
|
125
|
+
));
|
|
120
126
|
default:
|
|
121
|
-
throwRegistrationError(payload, "Lark app creation failed");
|
|
127
|
+
throwRegistrationError(payload, cliText("Lark app creation failed", "飞书应用创建失败"));
|
|
122
128
|
}
|
|
123
129
|
}
|
|
124
130
|
|
|
125
|
-
throw new Error(
|
|
131
|
+
throw new Error(cliText(
|
|
132
|
+
"Timed out waiting for Lark app creation. Run agent-bot init again.",
|
|
133
|
+
"等待飞书应用创建超时,请重新运行 agent-bot init。",
|
|
134
|
+
));
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
function buildVerificationUrl(userCode: string): string {
|
|
@@ -152,7 +161,10 @@ async function postRegistration(
|
|
|
152
161
|
});
|
|
153
162
|
const payload = (await response.json().catch(() => undefined)) as unknown;
|
|
154
163
|
if (!isRecord(payload)) {
|
|
155
|
-
throw new Error(
|
|
164
|
+
throw new Error(cliText(
|
|
165
|
+
`The Lark app registration endpoint returned an invalid response (HTTP ${response.status}).`,
|
|
166
|
+
`飞书应用注册接口返回了无效响应(HTTP ${response.status})。`,
|
|
167
|
+
));
|
|
156
168
|
}
|
|
157
169
|
return payload;
|
|
158
170
|
} finally {
|
|
@@ -165,7 +177,7 @@ function throwRegistrationError(payload: RegistrationPayload, prefix: string): v
|
|
|
165
177
|
const code = readString(payload, "error");
|
|
166
178
|
if (!code) return;
|
|
167
179
|
const description = readString(payload, "error_description") || code;
|
|
168
|
-
throw new Error(`${prefix}: ${description}`);
|
|
180
|
+
throw new Error(`${prefix}${cliText(": ", ":")}${description}`);
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
function readString(value: RegistrationPayload, key: string): string {
|
|
@@ -189,7 +201,9 @@ function isRecord(value: unknown): value is RegistrationPayload {
|
|
|
189
201
|
}
|
|
190
202
|
|
|
191
203
|
function abortError(signal: AbortSignal): Error {
|
|
192
|
-
return signal.reason instanceof Error
|
|
204
|
+
return signal.reason instanceof Error
|
|
205
|
+
? signal.reason
|
|
206
|
+
: new Error(cliText("Lark app creation was cancelled.", "飞书应用创建已取消。"));
|
|
193
207
|
}
|
|
194
208
|
|
|
195
209
|
function delay(milliseconds: number): Promise<void> {
|
|
@@ -218,3 +232,4 @@ async function sleepWithAbort(
|
|
|
218
232
|
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
219
233
|
}
|
|
220
234
|
}
|
|
235
|
+
import { cliText } from "./i18n.js";
|