@keyou007/agent-bot 0.1.9 → 0.1.11
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 +23 -0
- package/README.md +22 -10
- package/README.zh.md +22 -10
- package/assets/agent-bot-logo.png +0 -0
- package/dist/acp/AcpSessionManager.js +78 -46
- package/dist/acp/AcpSessionManager.js.map +1 -1
- package/dist/cli/FeishuAppConfiguration.js +84 -22
- 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/codex/AppServerConnection.js +15 -1
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexLocalActivityDetector.js +4 -1
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +1 -0
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +77 -2
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +108 -47
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +1 -1
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +345 -11
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +51 -5
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +8 -3
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/index.js +18 -17
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +595 -205
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +1 -0
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/runtime/AgentRuntimeRegistry.js +7 -4
- package/dist/runtime/AgentRuntimeRegistry.js.map +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.js +22 -0
- package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -0
- package/dist/startup/SessionMetadataHydrator.js +2 -2
- package/dist/startup/SessionMetadataHydrator.js.map +1 -1
- package/dist/startup/StartupNotifier.js +1 -0
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/state/StateStore.js +21 -15
- package/dist/state/StateStore.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +5 -0
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/docs/technical-reference.md +27 -6
- package/docs/technical-reference.zh.md +27 -6
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/skills/agent-bot/SKILL.md +19 -9
- package/src/acp/AcpSessionManager.ts +73 -54
- package/src/cli/FeishuAppConfiguration.ts +130 -18
- 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/codex/AppServerConnection.ts +21 -1
- package/src/codex/CodexLocalActivityDetector.ts +8 -1
- package/src/codex/CodexProcessManager.ts +1 -0
- package/src/codex/CodexRuntime.ts +91 -2
- package/src/commands/CommandRouter.ts +123 -46
- package/src/commands/commandTypes.ts +6 -11
- package/src/console/ConsoleTurnPresenter.ts +1 -1
- package/src/feishu/CardRenderer.ts +418 -11
- package/src/feishu/FeishuMessageClient.ts +66 -3
- package/src/feishu/FeishuTurnPresenter.ts +7 -2
- package/src/index.ts +21 -22
- package/src/proxy/ProxySessionController.ts +712 -208
- package/src/runtime/AcpRuntimeAdapter.ts +1 -0
- package/src/runtime/AgentRuntimeRegistry.ts +7 -6
- package/src/runtime/createAgentRuntimeRegistry.ts +25 -0
- package/src/runtime/types.ts +18 -0
- package/src/startup/SessionMetadataHydrator.ts +2 -2
- package/src/startup/StartupNotifier.ts +1 -0
- package/src/state/StateStore.ts +29 -15
- package/src/supervision/SafeRestartScheduler.ts +5 -0
|
@@ -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。
|
|
@@ -154,9 +162,11 @@ logging:
|
|
|
154
162
|
|
|
155
163
|
## Agent Runtime
|
|
156
164
|
|
|
157
|
-
`kind
|
|
165
|
+
Agent 配置的标准名是运行时隔离键。每个配置的 Agent 都拥有独立、按需启动的子进程和 Runtime 实例,即使多个 Agent 使用相同的 `kind` 也不会合并。不同 Agent 的任务不会共享命令、环境变量、协议连接、会话映射或事件流;同一 Agent 的多个任务共享该 Agent 进程,但使用独立的协议会话。
|
|
166
|
+
|
|
167
|
+
`kind` 只用于选择连接适配器。`kind: "codex"` 的每个 Agent 使用各自的 Codex App Server 进程。Agent Bot 通过 App Server 协议传递项目目录、模型、思考强度、权限模式、文字输入和本地图片。`/sessions` 会聚合所有 Codex Agent 返回的任务,同时保留任务所属 Agent,确保 Switch、Status、Stop、New 和 Fork 操作回到正确进程。
|
|
158
168
|
|
|
159
|
-
`kind: "acp"` 或未填写 `kind` 的 Agent
|
|
169
|
+
`kind: "acp"` 或未填写 `kind` 的 Agent 使用各自的 ACP 进程。同一 Agent 的多个任务会在该连接上创建独立 ACP session。Agent 配置中的 `env` 只会加入该 Agent 的环境变量。
|
|
160
170
|
|
|
161
171
|
所有 Agent 子进程都会收到:
|
|
162
172
|
|
|
@@ -181,6 +191,10 @@ AGENT_BOT=1
|
|
|
181
191
|
|
|
182
192
|
`/forkgroup` 会根据话题状态选择来源。话题尚未绑定任务,或者已绑定任务但尚未完成过自己的 turn 时,直接从话题原始锚点 turn fork,不创建中间话题任务。话题任务完成过 turn 后,使用本地持久化的最近完成 turn;更晚的执行中 turn 不会阻塞命令,也不会成为 fork 点。
|
|
183
193
|
|
|
194
|
+
Agent Bot 会在每个 `thread/fork` 请求中默认发送实验性的 `excludeTurns: true`。它只阻止响应填充 `thread.turns`,不会改变复制到分支中的历史。App Server 连接已启用 `experimentalApi`,不需要用户提供命令参数。如果旧版 App Server 明确提示 `excludeTurns` 字段未知、不支持或需要实验能力,Agent Bot 会移除该字段并重试一次。超时、断连和无关的 Fork 错误绝不重试,因为第一次请求可能已经创建分支。
|
|
195
|
+
|
|
196
|
+
新群欢迎消息会显示分支任务持久化后的 Provider、模型、思考强度和权限类型;权限类型显示为自动执行或执行前确认。
|
|
197
|
+
|
|
184
198
|
## Turn 与消息行为
|
|
185
199
|
|
|
186
200
|
- 路由没有当前任务时,普通文本会自动创建任务
|
|
@@ -188,13 +202,14 @@ AGENT_BOT=1
|
|
|
188
202
|
- Steering 与 turn 完成发生竞态时,文字变为下一条排队请求
|
|
189
203
|
- `/nosteer` 始终创建持久化 FIFO 队列项
|
|
190
204
|
- 队列卡操作可以取消单个待执行项
|
|
205
|
+
- 完整斜杠命令名优先;否则在解析参数前展开唯一的命令名前缀或已登记的复合命令首字母缩写,匹配多个命令时会拒绝并列出候选命令。当前缩写为 `fg` → `forkgroup`、`ng` → `newgroup`、`ns` → `nosteer`
|
|
191
206
|
- 未知斜杠命令会被拒绝,不会作为 Prompt 转发
|
|
192
207
|
|
|
193
208
|
每个 turn 只有一张进度卡。普通更新最多每两秒一次,关键更新最短间隔 500 毫秒。完成时先把进度卡更新为终态,再单独发送 Markdown 最终回答。
|
|
194
209
|
|
|
195
210
|
完成持久化消息去重占位后,Agent Bot 会等待 `OnIt` 表情添加成功,再进行聊天信息持久化、图片下载、队列等待、命令执行或 Runtime 调用。Turn 成功、失败或取消时分别替换为 `DONE`、`ERROR` 或 `CrossMark`。表情操作失败会记录日志,但不会阻塞任务。
|
|
196
211
|
|
|
197
|
-
富文本图片会下载到输入图片缓存,并以 `localImage` 传给 Codex。纯图片消息使用默认 Prompt
|
|
212
|
+
富文本图片会下载到输入图片缓存,并以 `localImage` 传给 Codex。纯图片消息使用默认 Prompt `请查看这张图片`。ACP Runtime 不支持图片输入时会明确报错。
|
|
198
213
|
|
|
199
214
|
## 任务、项目与外部 Codex 工作
|
|
200
215
|
|
|
@@ -202,6 +217,12 @@ AGENT_BOT=1
|
|
|
202
217
|
|
|
203
218
|
每个任务条目都提供 `NewGroup` 和 `ForkGroup` 回调。回调数据保留所选任务 ID 与来源上下文,并使用飞书操作者的 `open_id` 邀请用户进入新群。`NewGroup` 解析所选任务的项目和执行设置;`ForkGroup` 解析该任务最新可用的已完成 turn。
|
|
204
219
|
|
|
220
|
+
## Codex Provider 设置
|
|
221
|
+
|
|
222
|
+
Provider 是任务级设置,以 `model_provider` 与模型、思考强度和权限模式一起持久化。任务明确继承或选择 Provider 时,Agent Bot 通过 `thread/start`、`thread/resume` 和 `thread/fork` 传递 `modelProvider`。全新任务没有可继承 Provider 时,Agent Bot 省略该参数,由 Codex App Server 使用 Codex 配置中生效的 `model_provider`,再从 thread 响应中读取实际 Provider 并保存。
|
|
223
|
+
|
|
224
|
+
`/provider`、`/model`、`/thinking` 和 `/permissions` 打开同一张 Card 2.0 运行设置卡片,并激活对应的 tab。四个命令都拒绝参数,tab 切换和设置修改只通过卡片回调完成。Provider 选项来自 App Server 的 `config/read`;切换 Provider 时使用当前兼容的模型、思考强度和权限模式恢复 thread。模型、思考强度和权限选择会立即更新对应设置、就地刷新同一张卡片,并从下一次请求生效。
|
|
225
|
+
|
|
205
226
|
Agent Bot 内部保留本地路由键以关联飞书卡片和投递状态,但对用户展示 Codex 任务 ID。没有明确用户操作时,不会续写、steer、停止或分支其他客户端正在运行的 Codex 工作。
|
|
206
227
|
|
|
207
228
|
项目规则:
|
|
@@ -248,7 +269,7 @@ Supervisor、Worker、替换 Supervisor 和 Console Worker 默认启用 Node fat
|
|
|
248
269
|
2. 最终回答完成投递
|
|
249
270
|
3. 连续 15 秒没有新消息
|
|
250
271
|
|
|
251
|
-
|
|
272
|
+
飞书 `/restart` 命令默认使用这条安全重启路径;`/restart --force` 会立即重启,并可能中断正在执行的任务。该命令拒绝其他所有参数。新消息会重置静默计时,CLI 的 `--immediate` 和 `--force` 也会跳过这些检查。退出码 `75` 表示主动 worker 重启。
|
|
252
273
|
|
|
253
274
|
首次安全重启状态卡会延迟 3 秒发送,让任务最终回答尽可能先到达;延迟期间的状态变化会合并到首张卡片。该延迟不阻塞调度器轮询,真正关闭前会立即 flush 尚未发送的卡片。
|
|
254
275
|
|
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.11",
|
|
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.11",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@agentclientprotocol/sdk": "1.2.1",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyou007/agent-bot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Using Codex in Lark/Feishu feels just like using native Codex.",
|
|
6
6
|
"license": "GPL-3.0-only",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
".env.example",
|
|
28
|
+
"assets/",
|
|
28
29
|
"CHANGELOG.md",
|
|
29
30
|
"config.example.yaml",
|
|
30
31
|
"dist/",
|
|
@@ -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`, not merely the narrower group-mention scope.
|
|
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
|
|
|
@@ -99,6 +99,8 @@ agent-bot server stop
|
|
|
99
99
|
|
|
100
100
|
Treat `server restart` as the normal restart path. It schedules a safe restart and waits for active tasks, pending final-message delivery, and a quiet inbound-message window. The first safe-restart status card is delayed by three seconds so the active task's final response can usually be delivered first; this presentation delay does not postpone scheduler polling. While the restart is pending, its status card has a bottom `Cancel` button that conditionally cancels that exact schedule and updates the card in place.
|
|
101
101
|
|
|
102
|
+
In Feishu, `/restart` schedules the same safe restart. Use `/restart --force` only when the user explicitly accepts interruption; the command accepts no other arguments.
|
|
103
|
+
|
|
102
104
|
Use `agent-bot server restart --immediate` only when the user explicitly requests an immediate restart or accepts interruption. Never use `taskkill`, `Stop-Process`, or equivalent commands for routine Agent Bot restart management.
|
|
103
105
|
|
|
104
106
|
`agent-bot console` opens the console UI only when the service is stopped. Do not use `--force` while the service is live unless the user explicitly accepts concurrent state access.
|
|
@@ -135,25 +137,33 @@ Use `/queue <prompt>` in Feishu to persist a separate follow-up Prompt instead o
|
|
|
135
137
|
|
|
136
138
|
For a long-running objective in the active Feishu task, use `/goal <objective>`. Use `/goal` to inspect it, `/goal pause` or `/goal resume` to control automatic continuation, `/goal edit <objective>` to revise it, and `/goal clear` to remove it. Stopping a Goal turn through Agent Bot also pauses the Goal before sending Interrupt so it does not immediately continue.
|
|
137
139
|
|
|
138
|
-
Use `/
|
|
140
|
+
Use `/agent` in Feishu to open the unified execution-settings card on its Agent tab and `/agent <name>` to directly select the default agent. The Agent tab is present only when multiple agents are configured and remains usable before the chat has a current task. When only one Agent is configured, `/agent` reports that current Agent directly instead of sending a card. Agent selection affects future tasks and does not replace the Agent of an existing task; use `/new` after selecting when a new task is needed.
|
|
141
|
+
|
|
142
|
+
Use `/newgroup [title] [--dir <cwd> | --nodir]` in Feishu to create a private group named `[agent] [project directory] <title>`, invite the command sender, and immediately create and bind a new task in that group. `/new` and `/newgroup` share the same project options: an explicit `--dir` overrides the inherited project directory, while `--nodir` forces a Projectless Codex task; reject using both together. Resolve `~`, `~/...`, and `~\...` from the current user's home directory for both commands. Projectless groups omit the project segment and use `[agent] <title>`. The new task inherits the source chat's current task project directory when neither option is present, plus its model, reasoning effort, and permission mode without interrupting or switching the source task. When the source chat has no current task, use the selected agent and its runtime defaults. An explicit title becomes both the group suffix and the task title. When the title is omitted, use task title `新任务` and group name `[agent] [project directory] 新任务 (mm-dd)`, or `[agent] 新任务 (mm-dd)` for Projectless; an explicit title never gets a date suffix. Every new group gets a deterministic scheme-C Identicon avatar: Latin project names display their first word, Chinese names display up to their first four characters, and the normalized full project path hashes into the palette and symmetric node pattern. Projectless groups use the title as the stable seed. Replace the user's home-directory prefix with `~`. The bracketed project-directory value is limited to 15 characters. Longer paths keep the final two directories when they fit, otherwise keep the final directory, truncating the tail only when the final directory itself is overlong. Use `\` on Windows and `/` on macOS/Linux. Do not automatically send a Sessions or Status card to the new group. A later `/new --dir <cwd>` or `/new --nodir` in the group may explicitly replace the inherited project shape.
|
|
143
|
+
|
|
144
|
+
Provider is part of the same inherited execution-settings group as model, reasoning effort, and permission mode. Preserve all four across `/new`, `/newgroup`, `/fork`, `/forkgroup`, and the matching `/sessions` actions. New tasks with no inherited Provider must leave it unset so Codex App Server uses the default from Codex configuration. New-group welcome messages report all four settings.
|
|
139
145
|
|
|
140
146
|
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
147
|
|
|
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.
|
|
148
|
+
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
149
|
|
|
144
150
|
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
151
|
|
|
146
152
|
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
153
|
|
|
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.
|
|
154
|
+
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. Every fork request sends `excludeTurns: true` to Codex App Server so the response omits populated turns without removing turns from the forked history. Older App Servers that explicitly reject this experimental field are retried once without it; ambiguous failures such as timeouts or disconnects are not retried.
|
|
149
155
|
|
|
150
156
|
Status cards include a `Refresh` action that reads the latest task state and updates the same card in place without switching tasks or sending another card.
|
|
151
157
|
|
|
152
|
-
Use `/model` in Feishu to
|
|
158
|
+
Use `/agent`, `/provider`, `/model`, `/thinking`, or `/permissions` in Feishu to open the same interactive Card 2.0 execution-settings card with the corresponding tab active. The card exposes Provider, Model, Thinking, and Permission tabs whenever a current task exists, plus an Agent tab when multiple agents are configured. Each non-current option has an English `Switch` action. Selecting an option refreshes the same card in place. Agent changes affect future tasks; the other settings apply from the next request. Model changes automatically choose a compatible reasoning effort when necessary.
|
|
159
|
+
|
|
160
|
+
If the current runtime has no alternative Provider, `/provider` reports the current Provider directly instead of opening the settings card. Apply the same rule to `/agent` when only one Agent is configured.
|
|
161
|
+
|
|
162
|
+
These four settings commands do not accept arguments. Never instruct a user to send a Provider, model, reasoning effort, or permission value after the command; direct them to the card callbacks instead.
|
|
153
163
|
|
|
154
164
|
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
165
|
|
|
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.
|
|
166
|
+
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
167
|
|
|
158
168
|
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
169
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import type { Logger } from "pino";
|
|
3
|
-
import type {
|
|
3
|
+
import type { AgentConfig } from "../config/schema.js";
|
|
4
4
|
import type {
|
|
5
5
|
AcpInitializeResult,
|
|
6
6
|
AcpPermissionRequestParams,
|
|
@@ -13,7 +13,6 @@ import { AcpProcessManager, type ManagedAcpProcess } from "./AcpProcessManager.j
|
|
|
13
13
|
|
|
14
14
|
export interface RuntimeSession {
|
|
15
15
|
localSessionId: string;
|
|
16
|
-
processKey: string;
|
|
17
16
|
agentName: string;
|
|
18
17
|
cwd: string;
|
|
19
18
|
acpSessionId: string;
|
|
@@ -22,6 +21,8 @@ export interface RuntimeSession {
|
|
|
22
21
|
modes?: JsonValue;
|
|
23
22
|
configOptions?: JsonValue;
|
|
24
23
|
availableCommands?: JsonValue;
|
|
24
|
+
onUpdate: (session: RuntimeSession, update: Record<string, JsonValue>) => void;
|
|
25
|
+
onPermissionRequest: (session: RuntimeSession, params: AcpPermissionRequestParams) => Promise<JsonValue>;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export interface CreateRuntimeSessionInput {
|
|
@@ -38,9 +39,11 @@ export interface CreateRuntimeSessionInput {
|
|
|
38
39
|
export class AcpSessionManager {
|
|
39
40
|
private readonly sessions = new Map<string, RuntimeSession>();
|
|
40
41
|
private readonly sessionsByAcpId = new Map<string, RuntimeSession>();
|
|
42
|
+
private processStart?: Promise<ManagedAcpProcess>;
|
|
41
43
|
|
|
42
44
|
constructor(
|
|
43
|
-
private readonly
|
|
45
|
+
private readonly agentName: string,
|
|
46
|
+
private readonly agent: AgentConfig,
|
|
44
47
|
private readonly processManager: AcpProcessManager,
|
|
45
48
|
private readonly logger: Logger,
|
|
46
49
|
) {}
|
|
@@ -50,66 +53,19 @@ export class AcpSessionManager {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
async create(input: CreateRuntimeSessionInput): Promise<RuntimeSession> {
|
|
53
|
-
|
|
54
|
-
if (!agent) {
|
|
55
|
-
throw new Error(`Unknown agent: ${input.agentName}`);
|
|
56
|
-
}
|
|
56
|
+
if (input.agentName !== this.agentName) throw new Error(`Unexpected agent: ${input.agentName}`);
|
|
57
57
|
|
|
58
58
|
const cwd = path.resolve(input.cwd);
|
|
59
|
-
const
|
|
60
|
-
const managed = this.processManager.start(processKey, input.agentName, agent);
|
|
59
|
+
const managed = await this.process();
|
|
61
60
|
const connection = managed.connection;
|
|
62
61
|
|
|
63
|
-
let runtime: RuntimeSession | undefined;
|
|
64
|
-
|
|
65
|
-
connection.registerHandler("session/request_permission", async (params) => {
|
|
66
|
-
if (!runtime) {
|
|
67
|
-
throw new Error("Received permission request before session was ready.");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return input.onPermissionRequest(runtime, params as unknown as AcpPermissionRequestParams);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
connection.on("notification", (_method, params) => {
|
|
74
|
-
if (_method !== "session/update") {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const updateParams = params as unknown as AcpSessionUpdateParams;
|
|
79
|
-
const target = this.sessionsByAcpId.get(updateParams.sessionId);
|
|
80
|
-
if (!target) {
|
|
81
|
-
this.logger.debug({ updateParams }, "Ignoring update for unknown ACP session.");
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.applySessionUpdate(target, updateParams.update);
|
|
86
|
-
input.onUpdate(target, updateParams.update);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const initializeResult = await connection.request<AcpInitializeResult>("initialize", {
|
|
90
|
-
protocolVersion: 1,
|
|
91
|
-
clientCapabilities: {},
|
|
92
|
-
clientInfo: {
|
|
93
|
-
name: "agent-bot",
|
|
94
|
-
title: "Agent Bot",
|
|
95
|
-
version: "0.1.0",
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (initializeResult.authMethods?.length) {
|
|
100
|
-
const method = initializeResult.authMethods[0];
|
|
101
|
-
this.logger.info({ methodId: method.id }, "Authenticating ACP agent with first advertised method.");
|
|
102
|
-
await connection.request("authenticate", { methodId: method.id });
|
|
103
|
-
}
|
|
104
|
-
|
|
105
62
|
const newSessionResult = await connection.request<AcpSessionNewResult>("session/new", {
|
|
106
63
|
cwd,
|
|
107
64
|
mcpServers: [],
|
|
108
65
|
});
|
|
109
66
|
|
|
110
|
-
runtime = {
|
|
67
|
+
const runtime: RuntimeSession = {
|
|
111
68
|
localSessionId: input.localSessionId,
|
|
112
|
-
processKey,
|
|
113
69
|
agentName: input.agentName,
|
|
114
70
|
cwd,
|
|
115
71
|
acpSessionId: newSessionResult.sessionId,
|
|
@@ -117,6 +73,8 @@ export class AcpSessionManager {
|
|
|
117
73
|
running: false,
|
|
118
74
|
modes: newSessionResult.modes,
|
|
119
75
|
configOptions: newSessionResult.configOptions,
|
|
76
|
+
onUpdate: input.onUpdate,
|
|
77
|
+
onPermissionRequest: input.onPermissionRequest,
|
|
120
78
|
};
|
|
121
79
|
|
|
122
80
|
this.sessions.set(input.localSessionId, runtime);
|
|
@@ -169,7 +127,13 @@ export class AcpSessionManager {
|
|
|
169
127
|
|
|
170
128
|
this.sessions.delete(localSessionId);
|
|
171
129
|
this.sessionsByAcpId.delete(session.acpSessionId);
|
|
172
|
-
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
shutdown(): void {
|
|
133
|
+
this.sessions.clear();
|
|
134
|
+
this.sessionsByAcpId.clear();
|
|
135
|
+
this.processStart = undefined;
|
|
136
|
+
this.processManager.stopAll();
|
|
173
137
|
}
|
|
174
138
|
|
|
175
139
|
async setMode(localSessionId: string, modeId: string): Promise<void> {
|
|
@@ -203,6 +167,61 @@ export class AcpSessionManager {
|
|
|
203
167
|
return session;
|
|
204
168
|
}
|
|
205
169
|
|
|
170
|
+
private async process(): Promise<ManagedAcpProcess> {
|
|
171
|
+
const existing = this.processManager.get(this.agentName);
|
|
172
|
+
if (existing) return existing;
|
|
173
|
+
if (this.processStart) return this.processStart;
|
|
174
|
+
const start = this.startProcess();
|
|
175
|
+
this.processStart = start;
|
|
176
|
+
try {
|
|
177
|
+
return await start;
|
|
178
|
+
} finally {
|
|
179
|
+
if (this.processStart === start) this.processStart = undefined;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private async startProcess(): Promise<ManagedAcpProcess> {
|
|
184
|
+
const managed = this.processManager.start(this.agentName, this.agentName, this.agent);
|
|
185
|
+
try {
|
|
186
|
+
const connection = managed.connection;
|
|
187
|
+
connection.registerHandler("session/request_permission", async (params) => {
|
|
188
|
+
const permission = params as unknown as AcpPermissionRequestParams;
|
|
189
|
+
const target = this.sessionsByAcpId.get(permission.sessionId);
|
|
190
|
+
if (!target) throw new Error(`Unknown ACP session requesting permission: ${permission.sessionId}`);
|
|
191
|
+
return target.onPermissionRequest(target, permission);
|
|
192
|
+
});
|
|
193
|
+
connection.on("notification", (method, params) => {
|
|
194
|
+
if (method !== "session/update") return;
|
|
195
|
+
const updateParams = params as unknown as AcpSessionUpdateParams;
|
|
196
|
+
const target = this.sessionsByAcpId.get(updateParams.sessionId);
|
|
197
|
+
if (!target) {
|
|
198
|
+
this.logger.debug({ updateParams }, "Ignoring update for unknown ACP session.");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.applySessionUpdate(target, updateParams.update);
|
|
202
|
+
target.onUpdate(target, updateParams.update);
|
|
203
|
+
});
|
|
204
|
+
const initializeResult = await connection.request<AcpInitializeResult>("initialize", {
|
|
205
|
+
protocolVersion: 1,
|
|
206
|
+
clientCapabilities: {},
|
|
207
|
+
clientInfo: {
|
|
208
|
+
name: "agent-bot",
|
|
209
|
+
title: "Agent Bot",
|
|
210
|
+
version: "0.1.0",
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
if (initializeResult.authMethods?.length) {
|
|
214
|
+
const method = initializeResult.authMethods[0];
|
|
215
|
+
this.logger.info({ methodId: method.id }, "Authenticating ACP agent with first advertised method.");
|
|
216
|
+
await connection.request("authenticate", { methodId: method.id });
|
|
217
|
+
}
|
|
218
|
+
return managed;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
this.processManager.stop(this.agentName);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
206
225
|
private applySessionUpdate(session: RuntimeSession, update: Record<string, JsonValue>): void {
|
|
207
226
|
const updateType = update.sessionUpdate;
|
|
208
227
|
if (updateType === "config_option_update" && "configOptions" in update) {
|