@keyou007/agent-bot 0.1.13-alpha.0 → 0.1.13-alpha.2

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.
Files changed (97) hide show
  1. package/.env.example +2 -2
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +107 -98
  4. package/README.zh.md +81 -72
  5. package/dist/acp/AcpProcessManager.js +8 -3
  6. package/dist/acp/AcpProcessManager.js.map +1 -1
  7. package/dist/acp/AcpSessionManager.js +15 -0
  8. package/dist/acp/AcpSessionManager.js.map +1 -1
  9. package/dist/cli/ServerStarter.js +17 -2
  10. package/dist/cli/ServerStarter.js.map +1 -1
  11. package/dist/cli/controlProtocol.js.map +1 -1
  12. package/dist/cli/help.js +16 -4
  13. package/dist/cli/help.js.map +1 -1
  14. package/dist/cli/serverStatus.js +27 -0
  15. package/dist/cli/serverStatus.js.map +1 -1
  16. package/dist/cli/taskListOutput.js +72 -0
  17. package/dist/cli/taskListOutput.js.map +1 -0
  18. package/dist/cli.js +88 -54
  19. package/dist/cli.js.map +1 -1
  20. package/dist/codex/CodexProcessManager.js +34 -4
  21. package/dist/codex/CodexProcessManager.js.map +1 -1
  22. package/dist/codex/CodexRuntime.js +3 -0
  23. package/dist/codex/CodexRuntime.js.map +1 -1
  24. package/dist/commands/CommandRouter.js +5 -0
  25. package/dist/commands/CommandRouter.js.map +1 -1
  26. package/dist/config/FeishuUserOpenIdStore.js +76 -0
  27. package/dist/config/FeishuUserOpenIdStore.js.map +1 -0
  28. package/dist/config/loadConfig.js +3 -0
  29. package/dist/config/loadConfig.js.map +1 -1
  30. package/dist/feishu/CardRenderer.js +123 -22
  31. package/dist/feishu/CardRenderer.js.map +1 -1
  32. package/dist/feishu/FeishuConnector.js +16 -5
  33. package/dist/feishu/FeishuConnector.js.map +1 -1
  34. package/dist/index.js +125 -26
  35. package/dist/index.js.map +1 -1
  36. package/dist/presentation/turnGraph.js +36 -0
  37. package/dist/presentation/turnGraph.js.map +1 -0
  38. package/dist/proxy/ProxySessionController.js +425 -104
  39. package/dist/proxy/ProxySessionController.js.map +1 -1
  40. package/dist/runtime/AcpRuntimeAdapter.js +3 -0
  41. package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
  42. package/dist/runtime/agentEnvironment.js +24 -4
  43. package/dist/runtime/agentEnvironment.js.map +1 -1
  44. package/dist/runtime/createAgentRuntimeRegistry.js +7 -3
  45. package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -1
  46. package/dist/startup/StartupNotifier.js +16 -7
  47. package/dist/startup/StartupNotifier.js.map +1 -1
  48. package/dist/startup/startFeishu.js +2 -2
  49. package/dist/startup/startFeishu.js.map +1 -1
  50. package/dist/state/StateStore.js +172 -0
  51. package/dist/state/StateStore.js.map +1 -1
  52. package/dist/state/migrations.js +21 -0
  53. package/dist/state/migrations.js.map +1 -1
  54. package/dist/supervision/SafeRestartNotifier.js +46 -3
  55. package/dist/supervision/SafeRestartNotifier.js.map +1 -1
  56. package/dist/supervision/SafeRestartScheduler.js +65 -6
  57. package/dist/supervision/SafeRestartScheduler.js.map +1 -1
  58. package/dist/supervision/replacementSupervisor.js +27 -2
  59. package/dist/supervision/replacementSupervisor.js.map +1 -1
  60. package/dist/supervisor.js +27 -2
  61. package/dist/supervisor.js.map +1 -1
  62. package/docs/technical-reference.md +20 -7
  63. package/docs/technical-reference.zh.md +20 -7
  64. package/npm-shrinkwrap.json +2 -2
  65. package/package.json +1 -1
  66. package/skills/agent-bot/SKILL.md +63 -26
  67. package/src/acp/AcpProcessManager.ts +16 -3
  68. package/src/acp/AcpSessionManager.ts +14 -0
  69. package/src/cli/ServerStarter.ts +25 -2
  70. package/src/cli/controlProtocol.ts +6 -1
  71. package/src/cli/help.ts +16 -4
  72. package/src/cli/serverStatus.ts +28 -0
  73. package/src/cli/taskListOutput.ts +91 -0
  74. package/src/cli.ts +123 -51
  75. package/src/codex/CodexProcessManager.ts +38 -3
  76. package/src/codex/CodexRuntime.ts +6 -0
  77. package/src/commands/CommandRouter.ts +4 -0
  78. package/src/commands/commandTypes.ts +1 -0
  79. package/src/config/FeishuUserOpenIdStore.ts +81 -0
  80. package/src/config/loadConfig.ts +3 -0
  81. package/src/feishu/CardRenderer.ts +169 -22
  82. package/src/feishu/FeishuConnector.ts +13 -4
  83. package/src/index.ts +195 -23
  84. package/src/presentation/turnGraph.ts +48 -0
  85. package/src/proxy/ProxySessionController.ts +507 -112
  86. package/src/runtime/AcpRuntimeAdapter.ts +5 -0
  87. package/src/runtime/agentEnvironment.ts +41 -3
  88. package/src/runtime/createAgentRuntimeRegistry.ts +18 -3
  89. package/src/runtime/types.ts +5 -0
  90. package/src/startup/StartupNotifier.ts +23 -7
  91. package/src/startup/startFeishu.ts +3 -2
  92. package/src/state/StateStore.ts +224 -0
  93. package/src/state/migrations.ts +21 -0
  94. package/src/supervision/SafeRestartNotifier.ts +53 -4
  95. package/src/supervision/SafeRestartScheduler.ts +78 -7
  96. package/src/supervision/replacementSupervisor.ts +27 -1
  97. package/src/supervisor.ts +33 -2
@@ -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 通过 Node.js 国际化能力读取系统 Locale。以 `zh` 开头的 Locale 使用中文界面;英文及所有未支持 Locale 使用英文。帮助、状态、进度、交互提示和 CLI 自身错误都遵循此规则;JSON 字段名及枚举值不做本地化。系统生成的重启原因仍使用中文,因为它会显示在中文 Lark 状态卡中;显式传入的 `--reason` 保持原样。`agentbot server status` 会报告运行中 Worker 的 Lark App ID,JSON 字段名为 `feishuAppId`;服务停止或旧版健康协议尚未提供该字段时,CLI 会回退到当前 Profile 配置的 App ID。
38
+ CLI 通过 Node.js 国际化能力读取系统 Locale。以 `zh` 开头的 Locale 使用中文界面;英文及所有未支持 Locale 使用英文。帮助、状态、进度、交互提示和 CLI 自身错误都遵循此规则;JSON 字段名及枚举值不做本地化。系统生成的重启原因仍使用中文,因为它会显示在中文 Lark 状态卡中;显式传入的 `--reason` 保持原样。`agentbot server status` 会报告运行中 Worker 的 Lark App ID,JSON 字段名为 `feishuAppId`;服务停止或旧版健康协议尚未提供该字段时,CLI 会回退到当前 Profile 配置的 App ID。`agents` 数组会报告每个已配置 Agent 进程的 PID 和协议初始化返回的版本号;Agent 尚未启动时两者均为 `null`。
39
39
 
40
40
  | 默认路径 | 内容 |
41
41
  | ------------------------------------ | -------------------- |
@@ -73,6 +73,7 @@ CLI 通过 Node.js 国际化能力读取系统 Locale。以 `zh` 开头的 Local
73
73
  - 进程环境变量优先于 `~/.agent-bot/.env`
74
74
  - `FEISHU_APP_ID` 和 `FEISHU_APP_SECRET` 必须同时存在
75
75
  - 一键注册还会把授权用户的 `open_id` 保存为 `FEISHU_USER_OPEN_ID`
76
+ - 已有应用凭据但缺少用户 Open ID 时,第一条发送者为有效 `ou_` 标识的私聊消息会把该用户原子写入 Profile 的 `.env`;群消息和后续用户都不会覆盖它
76
77
  - 除非使用 `--reconfigure-feishu`,完整凭据不会被替换
77
78
  - 缺少凭据或只有一项时会重新创建应用
78
79
  - 新创建的凭据经过 fsync、原子替换写入 `.env`,并在配置权限前读回校验
@@ -100,7 +101,7 @@ https://open.feishu.cn/app/<appId>/auth?q=im%3Amessage.group_msg&op_from=openapi
100
101
 
101
102
  `agentbot --profile <目录> init --reset` 会完整重置显式指定的 Profile。执行前必须先停止该 Profile 的 Server。命令会把当前 `config.yaml`、`.env`、`data/` 和 `logs/` 移入 `<profile>/.reset-backups/` 下唯一的时间戳目录,从随包模板创建干净的新文件和目录,再继续正常初始化。已有重置备份及其他无关文件会保留。备份中可能含有旧 App Secret 和会话数据,应妥善保护 Profile 目录。远端旧飞书应用不会被删除。`--reset --skip-feishu` 可创建干净的 Console-only Profile,`--reset` 不能与 `--reconfigure-feishu` 同时使用。
102
103
 
103
- 飞书初始化成功后,CLI 会先释放初始化锁,再通过与 `agentbot server start` 相同的就绪检查流程启动后台 Supervisor,并等待最多 45 秒,直到 Worker 连接飞书并进入就绪状态。如果当前 Profile 的服务已经运行,不会创建第二个 Supervisor。`--skip-feishu` 会跳过自动启动;`--json` 会把结果写入 `server.status`,不会混入非 JSON 文本。
104
+ 飞书初始化成功后,CLI 会先释放初始化锁,再通过与 `agentbot server start` 相同的就绪检查流程启动后台 Supervisor,并等待最多 45 秒,直到 Worker 连接飞书并进入就绪状态。如果当前 Profile 的服务已经运行,不会创建第二个 Supervisor,而是安排安全重启,在活动任务和最终结果投递完成后加载当前安装的代码及更新后的配置;JSON 结果将其表示为 `server.status: "restart-scheduled"`。`--skip-feishu` 会跳过自动启动;`--json` 会把结果写入 `server.status`,不会混入非 JSON 文本。
104
105
 
105
106
  ## 飞书应用要求
106
107
 
@@ -171,7 +172,7 @@ logging:
171
172
 
172
173
  `feishu.respondToAllGroupMessages` 默认为 `true`。设为 `false` 后,未 @ 当前机器人的群消息会被忽略;Worker 启动时会解析机器人的 Open ID,因此 @ 其他成员不会误触发。私聊始终正常处理。无论该运行时配置如何,初始化都会申请接收全部群消息的权限,之后切换配置无需再次授权。
173
174
 
174
- `agentbot server start` 要求同时配置飞书 `appId` 和 `appSecret`。Worker 启动 SDK 长连接时不读取 SDK 日志或私有连接状态,随后通过发送启动状态卡片检查出站能力。每张启动卡片都会显示从已安装包元数据读取的 Agent Bot 版本。通知通常发往已知私聊和最近活跃的群聊;数据库里尚无已知会话时,改用 `feishu.userOpenId`,按 `open_id` 私聊发送。存在通知目标时,至少一张卡片发送成功后 Server 才报告就绪,单个目标发送失败仍相互隔离。如果既没有已知会话,也没有 `feishu.userOpenId`,启动会跳过出站检查并继续。缺少凭据时仍会启动失败并提示先初始化。`agentbot console` 是明确的纯本地入口,不需要飞书凭据。
175
+ `agentbot server start` 要求同时配置飞书 `appId` 和 `appSecret`。Worker 启动 SDK 长连接时不读取 SDK 日志或私有连接状态,随后通过发送启动状态卡片检查出站能力。每张启动卡片都会显示从已安装包元数据读取的 Agent Bot 版本。每次启动都会向所有已知私聊发送卡片,不受最近活跃时间影响;同时向本次 Worker 启动前 1 分钟内活跃的非话题群聊发送卡片。安全重启后,还会向所有已加入本次重启通知范围的群发送,不受活跃时间限制;话题路由会折算为所属普通群,因此话题会话永远不会成为启动卡目标。数据库里尚无符合条件的会话时,改用 `feishu.userOpenId`,按 `open_id` 私聊发送。存在通知目标时,至少一张卡片发送成功后 Server 才报告就绪,单个目标发送失败仍相互隔离。如果既没有已知会话,也没有 `feishu.userOpenId`,启动会跳过出站检查并继续;之后收到的第一条私聊消息可以补全用户 Open ID,供后续启动通知和 CLI 建群使用。缺少凭据时仍会启动失败并提示先初始化。`agentbot console` 是明确的纯本地入口,不需要飞书凭据。
175
176
 
176
177
  必须至少配置一个 Agent,`defaults.agent` 必须指向已配置的 Agent。
177
178
 
@@ -183,13 +184,19 @@ Agent 配置的标准名是运行时隔离键。每个配置的 Agent 都拥有
183
184
 
184
185
  `kind: "acp"` 或未填写 `kind` 的 Agent 使用各自的 ACP 进程。同一 Agent 的多个任务会在该连接上创建独立 ACP session。Agent 配置中的 `env` 只会加入该 Agent 的环境变量。
185
186
 
186
- 所有 Agent 子进程都会收到:
187
+ Agent 进程会继承普通父进程变量。启动 App Server 或 ACP 进程前,Agent Bot 会移除继承环境及 Agent 配置中名称不区分大小写匹配 `FEISHU_*` 或 `AGENT_BOT_*` 的全部变量,然后注入以下受控的非敏感上下文:
187
188
 
188
189
  ```text
189
190
  AGENT_BOT=1
191
+ AGENT_BOT_HOME=<当前 Profile 根目录>
192
+ AGENT_BOT_CONFIG=<当前 config.yaml 路径>
193
+ AGENT_BOT_AGENT_NAME=<Agent 配置标准名>
194
+ AGENT_BOT_LARK_APP_ID=<Lark App ID>
195
+ AGENT_BOT_LARK_BOT_OPEN_ID=<Lark 机器人 open_id,可用时提供>
196
+ AGENT_BOT_LARK_USER_OPEN_ID=<已保存的授权用户 open_id,可用时提供>
190
197
  ```
191
198
 
192
- 内置 Skill 使用该环境变量判断自己是否运行在 Agent Bot 中。
199
+ 内置 Skill 使用 `AGENT_BOT` 判断自己是否运行在 Agent Bot 中。Lark App Secret、Supervisor 状态、重启原因和重启通知路由不会传给 Agent。`agents.<name>.env` 中不使用保留命名空间的变量,仍是显式配置该 Agent 专用环境的方式。
193
200
 
194
201
  ## 聊天路由
195
202
 
@@ -222,13 +229,15 @@ Agent Bot 会在每个 `thread/fork` 请求中默认发送实验性的 `excludeT
222
229
 
223
230
  每个 turn 只有一张进度卡。普通更新最多每两秒一次,关键更新最短间隔 500 毫秒。完成时先把进度卡更新为终态,再单独发送 Markdown 最终回答。
224
231
 
232
+ 成功完成的思考卡片包含 `Reset` 操作,其作用提示紧跟在按钮下方。`/turns` 卡片把相同的作用提示放在最上方,然后按时间倒序显示当前任务的已完成 turn,每页 10 条。每次 `turn_started` 都会把前一个已完成 turn 保存为父节点;现有任务会结合快照时间和 `session_reset_to_turn` 审计记录回填,因此历史 Reset 后的第一轮会指向所选 turn,而不是时间上相邻的废弃路径。渲染器先基于完整历史计算 lane,再切分页面,从而跨分页正确显示延续和合并;内容缩进到第二列,状态或操作位于第三列。当前对话位置显示“当前”标记,其余 turn 提供 `Reset` 按钮。翻页和成功的 Reset 都会更新同一张卡片、保持与打开卡片时的任务绑定,并把“当前”标记移动到所选 turn;成功提示会用 Prompt 摘要、完成时间和 Turn ID 标明目标轮次。Agent Bot 会持久化每个 turn 原本所属的 App Server thread,从所选 turn fork,并替换当前任务的远端 thread 绑定;本地任务 ID、标题、Agent、项目目录、运行设置和聊天路由保持不变。所选 turn 之后已经完成的快照不会被删除,因此卡片会同时显示旧路径保留的轮次和 Reset 后新分支产生的轮次。当前任务仍在执行时会拒绝 Reset,本地文件修改也不会被回退。
233
+
225
234
  完成持久化消息去重占位后,Agent Bot 会等待 `OnIt` 表情添加成功,再进行聊天信息持久化、图片下载、队列等待、命令执行或 Runtime 调用。Turn 成功、失败或取消时分别替换为 `DONE`、`ERROR` 或 `CrossMark`。表情操作失败会记录日志,但不会阻塞任务。
226
235
 
227
236
  富文本图片会下载到输入图片缓存,并以 `localImage` 传给 App Server。纯图片消息使用默认 Prompt `请查看这张图片`。ACP Runtime 不支持图片输入时会明确报错。
228
237
 
229
238
  ## 任务、项目与外部 App Server 工作
230
239
 
231
- `/sessions` 通过 `thread/list` 读取每个已配置 App Server Agent 的任务。对于 Codex,可发现同一 `CODEX_HOME` 下由 Codex Desktop、CLI、Agent Bot 或其他 App Server 客户端创建的任务;其他 Agent 通过同一协议暴露各自的任务存储。
240
+ `/sessions` 通过 `thread/list` 读取每个已配置 App Server Agent 的任务。对于 Codex,可发现同一 `CODEX_HOME` 下由 Codex Desktop、CLI、Agent Bot 或其他 App Server 客户端创建的任务;其他 Agent 通过同一协议暴露各自的任务存储。多 Agent 任务合并并全局排序后,每页显示 5 个;`Previous` 和 `Next` 会替换当前卡片内容而不是继续追加任务,同时保留搜索条件和全局任务序号。
232
241
 
233
242
  每个任务条目都提供 `NewGroup` 和 `ForkGroup` 回调。回调数据保留所选任务 ID 与来源上下文,并使用飞书操作者的 `open_id` 邀请用户进入新群。`NewGroup` 解析所选任务的项目和执行设置;`ForkGroup` 解析该任务最新可用的已完成 turn。
234
243
 
@@ -290,9 +299,13 @@ Supervisor、Worker、替换 Supervisor 和 Console Worker 默认启用 Node fat
290
299
 
291
300
  飞书 `/restart` 命令默认使用这条安全重启路径;`/restart --force` 会立即重启,并可能中断正在执行的任务。该命令拒绝其他所有参数。新消息会重置静默计时,CLI 的 `--immediate` 和 `--force` 也会跳过这些检查。退出码 `75` 表示主动 worker 重启。
292
301
 
302
+ 待执行的安全重启计划会收集每一个触发它的精确会话路由。收到任何用户消息时,包括斜杠命令,都会把所属基础私聊或群聊标为活跃;每次状态更新还会加入此前 1 分钟内活跃的所有已知会话。每个已加入的会话都会收到各自的状态卡片和真正开始重启前的提示,而且即使之后超过 1 分钟未活跃,也会一直保留到本次计划结束。同一路由会去重;如果话题路由最初缺少消息锚点,后续带锚点的触发会补全它。话题请求会保留原消息 ID,并通过话题回复发送,避免重启状态落到群主会话;话题中的最近活动记在所属普通群,只有显式从话题触发时才向该话题本身发送。后续请求也会更新重启原因。替换进程前,Agent Bot 会提取并去重所有已加入路由所属的普通群,经替换 Supervisor 传给 Worker,再把这些群加入服务级启动卡目标。Supervisor 会保留该列表,直到 Worker 连续稳定运行 60 秒,避免启动阶段异常退出并重新拉起时丢失必发群。
303
+
304
+ 当 CLI 重启属于某个具体任务时,使用 `agentbot server restart --task <任务>`。任务序号、完整 ID 和不冲突的 ID 前缀会在发送控制请求前解析。未传 `--task` 时,只有所有运行中任务都属于同一个会话,Server 才会自动推断安全重启状态的发送目标;如果同时有多个运行中的会话,请求会被拒绝。没有运行中任务的本地 CLI 重启没有等待状态所属会话。
305
+
293
306
  首次安全重启状态卡会延迟 3 秒发送,让任务最终回答尽可能先到达;延迟期间的状态变化会合并到首张卡片。该延迟不阻塞调度器轮询,真正关闭前会立即 flush 尚未发送的卡片。
294
307
 
295
- 等待中的安全重启状态卡底部带有 `Cancel` 操作。回调会携带调度器单调递增的计划 ID,因此旧卡片不会取消较新的重启。取消成功后,所有已发送的状态卡都会原地更新并移除按钮;调度器进入不可逆的正在重启阶段后也不再显示该按钮。
308
+ 等待中的安全重启状态卡底部带有 `Cancel` 操作。回调会携带调度器单调递增的计划 ID,因此旧卡片不会取消较新的重启。取消成功后,所有已加入会话中的状态卡都会原地更新并移除按钮;调度器进入不可逆的正在重启阶段后也不再显示该按钮。
296
309
 
297
310
  本地控制服务负责修改任务、执行指定任务的 NewGroup 和 ForkGroup,以及请求服务重启;只读任务查询直接访问 SQLite。
298
311
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@keyou007/agent-bot",
3
- "version": "0.1.13-alpha.0",
3
+ "version": "0.1.13-alpha.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@keyou007/agent-bot",
9
- "version": "0.1.13-alpha.0",
9
+ "version": "0.1.13-alpha.2",
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.13-alpha.0",
3
+ "version": "0.1.13-alpha.2",
4
4
  "type": "module",
5
5
  "description": "Use Codex, TraeX, and compatible coding agents from Lark/Feishu.",
6
6
  "license": "GPL-3.0-only",
@@ -31,6 +31,19 @@ running_in_agent_bot = os.environ.get("AGENT_BOT") == "1"
31
31
 
32
32
  Treat this environment marker as authoritative. Do not infer Agent Bot execution merely because the CLI is installed, `~/.agent-bot` exists, or an Agent Bot process is running elsewhere. Configured agent environment variables cannot override the marker. Child processes normally inherit it, so the marker identifies the Agent Bot-started process tree unless a child explicitly replaces its environment.
33
33
 
34
+ Agent Bot preserves ordinary parent-process variables but removes inherited and Agent-configured `FEISHU_*` and `AGENT_BOT_*` variables before starting an Agent. It then exposes only this controlled, non-secret context when values are available:
35
+
36
+ ```text
37
+ AGENT_BOT_HOME
38
+ AGENT_BOT_CONFIG
39
+ AGENT_BOT_AGENT_NAME
40
+ AGENT_BOT_LARK_APP_ID
41
+ AGENT_BOT_LARK_BOT_OPEN_ID
42
+ AGENT_BOT_LARK_USER_OPEN_ID
43
+ ```
44
+
45
+ `AGENT_BOT_HOME` is the active Profile root. Never expect `FEISHU_APP_SECRET`, Supervisor state, restart reasons, or restart notification routes in the Agent environment.
46
+
34
47
  ## Behave safely inside Agent Bot
35
48
 
36
49
  When `AGENT_BOT=1`:
@@ -52,7 +65,9 @@ After the maintenance choice, interactive initialization requires the user to se
52
65
 
53
66
  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.
54
67
 
55
- Rerun `agentbot init` after upgrading Agent Bot. It non-destructively upgrades an existing `config.yaml` by adding settings missing from the current bundled template while preserving user values, comments, custom Agents, and intentionally omitted template Agents. It also appends active variables missing from the bundled `.env.example` without changing existing values, comments, line endings, or enabling commented optional variables. It repeats the Agent version checks and Feishu configuration audit. Invalid YAML is left untouched and reported as an error. Use `init --reset` only for an explicitly requested full Profile reset.
68
+ An existing app may have complete credentials but no `FEISHU_USER_OPEN_ID`. In that state, ask the user to send the bot a direct message. Agent Bot atomically persists the first valid private-chat sender to the Profile `.env`; group messages and later users cannot replace it. This identity is needed for startup-notification fallback and CLI-created group invitations.
69
+
70
+ Rerun `agentbot init` after upgrading Agent Bot. It non-destructively upgrades an existing `config.yaml` by adding settings missing from the current bundled template while preserving user values, comments, custom Agents, and intentionally omitted template Agents. It also appends active variables missing from the bundled `.env.example` without changing existing values, comments, line endings, or enabling commented optional variables. It repeats the Agent version checks and Feishu configuration audit. If the selected Profile server is already running, initialization schedules a safe restart so the current installed code and refreshed configuration take effect after active work finishes. Invalid YAML is left untouched and reported as an error. Use `init --reset` only for an explicitly requested full Profile reset.
56
71
 
57
72
  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.
58
73
 
@@ -86,7 +101,7 @@ agentbot server status
86
101
  agentbot task list
87
102
  ```
88
103
 
89
- 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. `agentbot server status` also reports the running profile's Lark App ID; with `--json`, read it from `feishuAppId`.
104
+ 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. `agentbot server status` reports the running profile's Lark App ID and each configured Agent process's PID and initialized version. With `--json`, read them from `feishuAppId` and `agents`; an Agent that has not started has `null` process fields.
90
105
 
91
106
  Add `--json` to status and list commands when machine-readable output helps. If `agentbot` is unavailable, report that the CLI is not installed or linked instead of guessing process state.
92
107
 
@@ -99,12 +114,14 @@ Use these commands:
99
114
  ```powershell
100
115
  agentbot server start
101
116
  agentbot server status
102
- agentbot server restart --reason "reason for restart"
117
+ agentbot server restart --task <current-task-id> --reason "reason for restart"
103
118
  agentbot server stop
104
119
  ```
105
120
 
106
121
  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.
107
122
 
123
+ When scheduling from an Agent Bot-hosted task, pass `--task <current-task-id>`. This adds the task's exact Lark conversation, including its topic, to the pending restart's notification set. Every conversation that triggers the same pending restart plus every conversation active during the previous minute receives its own status card and restarting acknowledgement. Once enrolled, a route remains in the set until that restart ends; repeated routes are deduplicated. Resolve the current ID with `agentbot task list --status running` when necessary. If `--task` is omitted, the Server infers a target only when all running tasks belong to one conversation and rejects the request when multiple running conversations make ownership ambiguous. Startup cards are service-wide notices: every known private chat receives one on every startup, groups receive one when active in the previous minute or enrolled for the current safe restart, and topic routes receive the card in their parent group rather than inside the topic.
124
+
108
125
  On Windows, initial start, safe or immediate replacement, and every crash-driven Worker launch reload the latest Machine and User environment variables. After changing `PATH` or installing a CLI shim, finish the current work and use the normal restart path. Agent Bot keeps `AGENT_BOT_*` and `FEISHU_*` process-local so Profile selection and bot credentials do not leak between isolated instances.
109
126
 
110
127
  In Feishu, `/restart` schedules the same safe restart. Use `/restart --force` only when the user explicitly accepts interruption; the command accepts no other arguments.
@@ -128,6 +145,7 @@ List tasks before resolving a numeric reference because task numbers follow the
128
145
 
129
146
  ```powershell
130
147
  agentbot task list
148
+ agentbot task current [--json]
131
149
  agentbot task chat <number-or-task-id> [--json]
132
150
  agentbot task status <number-or-task-id>
133
151
  agentbot task stop <number-or-task-id>
@@ -139,59 +157,78 @@ agentbot task forkgroup <number-or-task-id> [title] [--json]
139
157
 
140
158
  Task IDs may be supplied in full or as an unambiguous prefix. Use `task stop` to ask the running worker to send the App Server Interrupt signal; leave child-process lifecycle management to the selected Agent.
141
159
 
160
+ Read all task identity fields from `task list`: every entry shows its Agent and `AgentBot task ID`; App Server tasks also show `App Server thread ID`, while ACP tasks show `ACP session ID`. The status, title, conversation context, and update time remain visible. The CLI reads `CODEX_THREAD_ID` for Codex and `TRAECLI_THREAD_ID` for TraeX; when the available native Thread IDs identify one task, it marks that entry as `Current`. Use `task current` to print that task's details directly. Use any displayed full ID or an unambiguous prefix as a task reference. With `--json`, read the local and native values from `localSessionId`, `remoteSessionId`, and `acpSessionId`.
161
+
142
162
  Use `task chat <number-or-task-id>` to read the Feishu chat ID associated with a task from local routing state. Plain output is only the chat ID for scripting. Add `--json` to include the stable task ID, complete context key, and the thread ID for a topic-bound task. This read-only command does not require the Agent Bot server to be running.
143
163
 
144
164
  Use `task prompt` to send input to a specific task without changing any chat's current task. The bot first posts the Prompt text to the task's existing Feishu chat, thread, or private-chat route, then submits it to the task. If posting fails, the task is not started or steered. The thinking card and final response continue to the same route; the command line only reports whether the Prompt was accepted.
145
165
 
146
- ### Create a task group from the CLI
166
+ ### Create or fork a group from the CLI
167
+
168
+ Choose by whether the new task needs the source conversation:
169
+
170
+ - Use **New Group** for a fresh task in a separate private Lark group. Inherit the project and execution settings, but do not carry over prior turns. Use it for a separate assignment in the same codebase or for a different project or Agent.
171
+ - Use **Fork Group** to continue or parallelize work with the source task's conversation through a completed turn. Preserve that context in a separate private Lark group.
172
+
173
+ Both operations create and bind the destination task immediately. Neither interrupts the source task nor changes the source chat's current task.
174
+
175
+ When creating or forking from the task that is currently running this Skill, run `agentbot task current --json` first and pass its `localSessionId` as `<number-or-task-id>`. The command resolves Codex through `CODEX_THREAD_ID` and TraeX through `TRAECLI_THREAD_ID`, while preferring the only running match when another Agent's variable was inherited from a parent process. If the command cannot identify one task, ask for an explicit AgentBot task ID instead of guessing from list order, title, or project directory.
147
176
 
148
- Use `task newgroup` to create a Lark group and bind a new task from outside Feishu. List tasks first and pass the intended source task explicitly; `<number-or-task-id>` defines the source context, so never substitute the shell's current working directory for the task's project directory.
177
+ List tasks first, identify the intended source explicitly, then run one of these commands:
149
178
 
150
179
  ```powershell
151
- # Inherit the source task's Agent, project, and execution settings.
180
+ agentbot task list
181
+
182
+ # Fresh task: inherit the source Agent, project, and execution settings.
152
183
  agentbot task newgroup <number-or-task-id> "Review"
153
184
 
154
- # Override the project directory. Prefer an absolute path or a ~-based path.
185
+ # Fresh task: override the inherited project directory.
155
186
  agentbot task newgroup <number-or-task-id> "Review" --dir ~/dev/project
156
187
 
157
- # Create a fresh Projectless App Server task instead of inheriting a project.
188
+ # Fresh task: use a Projectless App Server workspace.
158
189
  agentbot task newgroup <number-or-task-id> "Question" --nodir
159
190
 
160
- # Use another configured Agent with the inherited project shape.
191
+ # Fresh task: use another configured Agent and that Agent's defaults.
161
192
  agentbot task newgroup <number-or-task-id> "Review" --agent traex
193
+
194
+ # Context-preserving branch from the source task.
195
+ agentbot task forkgroup <number-or-task-id> "Parallel review"
162
196
  ```
163
197
 
164
- Apply these rules:
198
+ Apply these New Group rules:
165
199
 
166
- - With neither `--dir` nor `--nodir`, inherit the source task's project directory. If the source is Projectless, create a fresh Projectless workspace rather than reusing its generated directory.
167
- - `--dir <cwd>` overrides the inherited project and resolves `~`, `~/...`, and `~\...` from the user's home directory.
168
- - `--nodir` forces a Projectless task and is valid only for an App Server Agent. Reject combining it with `--dir`.
169
- - With no `--agent`, inherit the source task's Agent, Provider, model, reasoning effort, and permission mode. When `--agent <standard-name>` selects a different Agent, retain the chosen or inherited project shape but use that Agent runtime's own execution defaults.
170
- - The Server must be running. The Profile must contain `FEISHU_USER_OPEN_ID`; the Server invites that saved authorizing user because the CLI has no Lark operator identity.
171
- - Creating the group must not interrupt the source task or switch the source chat's current task. Add `--json` when the caller needs `group.chatId`, `group.contextKey`, or the created task IDs.
200
+ - Treat `<number-or-task-id>` as the source context; never substitute the shell's current working directory for its project directory.
201
+ - With neither `--dir` nor `--nodir`, inherit the source project. If it is Projectless, create a fresh Projectless workspace rather than reusing its generated directory.
202
+ - Resolve `~`, `~/...`, and `~\...` in `--dir <cwd>` from the user's home directory. Reject combining `--dir` with `--nodir`.
203
+ - Allow `--nodir` only for an App Server Agent.
204
+ - With no `--agent`, inherit the source Agent, Provider, model, reasoning effort, and permission mode. With `--agent <standard-name>`, preserve the selected project shape but use the selected Agent's runtime defaults. `--agent` is a CLI-only New Group option.
172
205
 
173
- Use `task forkgroup` to Fork the selected task's latest available completed turn into a new group. It does not interrupt a newer active source turn and does not switch the source chat. Both group commands accept an optional title and `--json`; use the structured result to read the new `group.chatId`, `group.contextKey`, and task IDs.
206
+ Apply these Fork Group rules:
174
207
 
175
- Use `/queue <prompt>` in Feishu to persist a separate follow-up Prompt instead of steering the active turn. Queued Prompts run in FIFO order after the current turn completes and survive Agent Bot restarts. `/nosteer <prompt>` is an equivalent compatibility spelling.
208
+ - Fork the latest available completed turn and preserve its conversation context. Never include or interrupt a newer active turn. Reject the request when no completed turn is available.
209
+ - Keep the source Agent, project, Provider, model, reasoning effort, and permission mode. Fork Group does not accept Agent or project overrides; use New Group when those must change.
210
+ - Use the optional title as the destination task title and group suffix.
176
211
 
177
- 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.
212
+ Require a running Server for either CLI command. Require `FEISHU_USER_OPEN_ID` so the Server can invite the saved authorizing user; the CLI has no Lark operator identity of its own. Add `--json` when the caller needs `group.chatId`, `group.contextKey`, or the created task IDs.
178
213
 
179
- 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.
214
+ Name a New Group without an explicit title as `[agent] [project directory] 新任务 (mm-dd)`, or `[agent] 新任务 (mm-dd)` when Projectless. Name an untitled Fork Group with the source task's persistent `source title(分支 N)` sequence, without a date suffix. Cap the displayed Lark group name at 60 characters by truncating only the group title portion; preserve the complete Agent Bot task title. Do not automatically send a Sessions or Status card to the destination group. Send a welcome message that reports Provider, model, reasoning effort, and permission mode.
180
215
 
181
- 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 App Server 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.
216
+ Give every created group a deterministic scheme-C Identicon avatar. Display the first word for Latin project names or up to four characters for Chinese names, and hash the normalized full project path into the palette and symmetric node pattern. Use the title as the stable seed for Projectless groups. Replace the user's home prefix with `~` in the group-name project segment. Limit that segment to 15 characters, preferring the final two directories, then the final directory, and only then tail truncation. Use `\` on Windows and `/` on macOS/Linux.
182
217
 
183
- 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 the selected App Server Agent uses its own default configuration. New-group welcome messages report all four settings.
218
+ Use `/queue <prompt>` in Feishu to persist a separate follow-up Prompt instead of steering the active turn. Queued Prompts run in FIFO order after the current turn completes and survive Agent Bot restarts. `/nosteer <prompt>` is an equivalent compatibility spelling.
184
219
 
185
- 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.
220
+ 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.
186
221
 
187
- 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 App Server 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.
222
+ 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.
188
223
 
189
224
  Use `/new [title] --nodir` to force a new App Server 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.
190
225
 
191
- 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.
226
+ The Feishu `/sessions` card shows five tasks per page and updates the same card through `Previous` and `Next`; it never expands earlier pages into one long card. Every task 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.
192
227
 
193
228
  Use `/fork [number-or-task-id]` in Feishu to branch from the current or specified App Server 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 the 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.
194
229
 
230
+ Successfully completed Feishu thinking cards include a bottom `Reset` action followed by its compact warning. `/turns` opens the current task's completed-turn history with the Reset-effect warning at the top and 10 turns per page. It renders each entry as an indented numbered node in a commit graph whose lanes follow persisted parent turns, including Reset branches and merges across page boundaries. A successful Reset refreshes that card in place, moves the current marker to the selected turn, and sends a confirmation containing the target Prompt summary, completion time, and Turn ID. Reset forks the selected turn's original App Server thread through that turn and replaces the current task's remote thread binding in place, preserving the local task ID, title, Agent, project directory, execution settings, and chat route. Completed snapshots after the selected point remain visible together with later turns from the new branch. Reset is available only when the card still belongs to the current task and the task is idle. It restores conversation context only and never reverts local files.
231
+
195
232
  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.
196
233
 
197
234
  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.
@@ -213,7 +250,7 @@ Use `--context <key>` or `--status <status>` to narrow `task list`. Use `--profi
213
250
  After changing Agent Bot code, run the relevant tests, typecheck, and build. If a running service must pick up the result, schedule:
214
251
 
215
252
  ```powershell
216
- agentbot server restart --reason "brief description of the completed change"
253
+ agentbot server restart --task <current-task-id> --reason "brief description of the completed change"
217
254
  ```
218
255
 
219
256
  Do not replace the worker immediately while a task is active. A later incoming task must not cancel the pending safe restart; allow the scheduler to wait until the service becomes safely idle again.
@@ -3,7 +3,10 @@ import readline from "node:readline";
3
3
  import type { Logger } from "pino";
4
4
  import type { AgentConfig } from "../config/schema.js";
5
5
  import { AcpJsonRpcConnection } from "./AcpJsonRpcConnection.js";
6
- import { agentBotEnvironment } from "../runtime/agentEnvironment.js";
6
+ import {
7
+ agentBotEnvironment,
8
+ type AgentEnvironmentContext,
9
+ } from "../runtime/agentEnvironment.js";
7
10
  import { spawnStdioCommand } from "../utils/spawnCommand.js";
8
11
 
9
12
  export interface ManagedAcpProcess {
@@ -16,14 +19,24 @@ export interface ManagedAcpProcess {
16
19
  export class AcpProcessManager {
17
20
  private readonly processes = new Map<string, ManagedAcpProcess>();
18
21
 
19
- constructor(private readonly logger: Logger) {}
22
+ constructor(
23
+ private readonly logger: Logger,
24
+ private readonly environmentContext: () => AgentEnvironmentContext = () => ({}),
25
+ ) {}
20
26
 
21
27
  start(processKey: string, agentName: string, agent: AgentConfig): ManagedAcpProcess {
22
28
  if (this.processes.has(processKey)) {
23
29
  throw new Error(`ACP process already exists: ${processKey}`);
24
30
  }
25
31
 
26
- const child = spawnStdioCommand(agent.command, agent.args, agentBotEnvironment(process.env, agent.env));
32
+ const child = spawnStdioCommand(
33
+ agent.command,
34
+ agent.args,
35
+ agentBotEnvironment(process.env, agent.env, {
36
+ ...this.environmentContext(),
37
+ agentName,
38
+ }),
39
+ );
27
40
 
28
41
  const childLogger = this.logger.child({ processKey, agentName });
29
42
  const connection = new AcpJsonRpcConnection(child, childLogger);
@@ -40,6 +40,7 @@ export class AcpSessionManager {
40
40
  private readonly sessions = new Map<string, RuntimeSession>();
41
41
  private readonly sessionsByAcpId = new Map<string, RuntimeSession>();
42
42
  private processStart?: Promise<ManagedAcpProcess>;
43
+ private processVersion?: string;
43
44
 
44
45
  constructor(
45
46
  private readonly agentName: string,
@@ -52,6 +53,15 @@ export class AcpSessionManager {
52
53
  return this.sessions.get(localSessionId);
53
54
  }
54
55
 
56
+ getProcessInfo(): { pid?: number; version?: string } {
57
+ const pid = this.processManager.get(this.agentName)?.child.pid;
58
+ if (!pid) return {};
59
+ return {
60
+ pid,
61
+ ...(this.processVersion ? { version: this.processVersion } : {}),
62
+ };
63
+ }
64
+
55
65
  async create(input: CreateRuntimeSessionInput): Promise<RuntimeSession> {
56
66
  if (input.agentName !== this.agentName) throw new Error(`Unexpected agent: ${input.agentName}`);
57
67
 
@@ -133,6 +143,7 @@ export class AcpSessionManager {
133
143
  this.sessions.clear();
134
144
  this.sessionsByAcpId.clear();
135
145
  this.processStart = undefined;
146
+ this.processVersion = undefined;
136
147
  this.processManager.stopAll();
137
148
  }
138
149
 
@@ -181,6 +192,7 @@ export class AcpSessionManager {
181
192
  }
182
193
 
183
194
  private async startProcess(): Promise<ManagedAcpProcess> {
195
+ this.processVersion = undefined;
184
196
  const managed = this.processManager.start(this.agentName, this.agentName, this.agent);
185
197
  try {
186
198
  const connection = managed.connection;
@@ -210,6 +222,8 @@ export class AcpSessionManager {
210
222
  version: "0.1.0",
211
223
  },
212
224
  });
225
+ const version = initializeResult.agentInfo?.version?.trim();
226
+ if (version) this.processVersion = version;
213
227
  if (initializeResult.authMethods?.length) {
214
228
  const method = initializeResult.authMethods[0];
215
229
  this.logger.info({ methodId: method.id }, "Authenticating ACP agent with first advertised method.");
@@ -11,7 +11,11 @@ import {
11
11
  import { refreshedSystemEnvironment } from "../supervision/systemEnvironment.js";
12
12
  import { controlEndpoint } from "./controlProtocol.js";
13
13
  import { cliText } from "./i18n.js";
14
- import { isServerReachable, isServerRunning } from "./LocalControlClient.js";
14
+ import {
15
+ isServerReachable,
16
+ isServerRunning,
17
+ sendControlRequest,
18
+ } from "./LocalControlClient.js";
15
19
 
16
20
  export interface ServerStartResult {
17
21
  status: "started" | "already-running";
@@ -19,6 +23,7 @@ export interface ServerStartResult {
19
23
 
20
24
  export type InitializationServerResult =
21
25
  | ServerStartResult
26
+ | { status: "restart-scheduled" }
22
27
  | { status: "skipped"; reason: "feishu-skipped" };
23
28
 
24
29
  export interface ServerStarterDependencies {
@@ -33,6 +38,7 @@ const SERVER_START_TIMEOUT_MS = 45_000;
33
38
  interface InitializationServerDependencies {
34
39
  loadConfig(configPath?: string): AppConfig;
35
40
  startServer(config: AppConfig): Promise<ServerStartResult>;
41
+ restartServer(config: AppConfig): Promise<void>;
36
42
  }
37
43
 
38
44
  export async function startInitializedServer(
@@ -42,7 +48,11 @@ export async function startInitializedServer(
42
48
  if (options.skipFeishu) {
43
49
  return { status: "skipped", reason: "feishu-skipped" };
44
50
  }
45
- return dependencies.startServer(dependencies.loadConfig(options.configPath));
51
+ const config = dependencies.loadConfig(options.configPath);
52
+ const result = await dependencies.startServer(config);
53
+ if (result.status !== "already-running") return result;
54
+ await dependencies.restartServer(config);
55
+ return { status: "restart-scheduled" };
46
56
  }
47
57
 
48
58
  export async function startServer(
@@ -116,6 +126,19 @@ const defaultDependencies: ServerStarterDependencies = {
116
126
  const defaultInitializationDependencies: InitializationServerDependencies = {
117
127
  loadConfig,
118
128
  startServer: (config) => startServer(config),
129
+ restartServer: async (config) => {
130
+ const response = await sendControlRequest(controlEndpoint(config.storage.sqlitePath), {
131
+ action: "server_restart",
132
+ mode: "safe",
133
+ reason: "Agent Bot 初始化完成,需要加载当前版本",
134
+ });
135
+ if (!response.ok) {
136
+ throw new Error(response.message?.trim() || cliText(
137
+ "Could not schedule the Agent Bot restart after initialization.",
138
+ "初始化完成后无法安排 Agent Bot 重启。",
139
+ ));
140
+ }
141
+ },
119
142
  };
120
143
 
121
144
  async function waitForServer(endpoint: string, timeoutMs: number): Promise<boolean> {
@@ -6,7 +6,12 @@ import type { SessionRecord } from "../state/StateStore.js";
6
6
 
7
7
  export type ControlRequest =
8
8
  | { action: "health" }
9
- | { action: "server_restart"; mode: "safe" | "immediate"; reason: string }
9
+ | {
10
+ action: "server_restart";
11
+ mode: "safe" | "immediate";
12
+ reason: string;
13
+ notificationSessionId?: string;
14
+ }
10
15
  | { action: "server_stop" }
11
16
  | { action: "task_status"; localSessionId: string }
12
17
  | { action: "task_stop"; localSessionId: string }
package/src/cli/help.ts CHANGED
@@ -13,10 +13,11 @@ export function renderCliHelp(version: string, language: CliLanguage = cliLangua
13
13
  init --reset 重置并重新配置显式指定的 Profile
14
14
  console 打开本地控制台
15
15
  server start 在后台启动 Agent Bot
16
- server status 显示服务、飞书 App ID 和安全重启状态
17
- server restart 安排安全重启
16
+ server status 显示服务、飞书 App ID、Agent 进程和安全重启状态
17
+ server restart [--task <任务>] 安排安全重启并将状态发回指定任务会话
18
18
  server stop 停止 Agent Bot
19
19
  task list 列出任务
20
+ task current [--json] 显示当前调用任务的详情
20
21
  task status <任务> 显示任务状态
21
22
  task chat <任务> 显示任务绑定的飞书会话
22
23
  task prompt <任务> <提示词> 向任务发送提示词
@@ -39,6 +40,11 @@ export function renderCliHelp(version: string, language: CliLanguage = cliLangua
39
40
  --skip-feishu 仅初始化 Console 环境
40
41
  --reconfigure-feishu 仅替换现有飞书凭据
41
42
 
43
+ 重启选项:
44
+ --task <任务> 将安全重启状态发回任务所在会话
45
+ --reason <原因> 设置重启原因
46
+ --immediate 立即重启,可能中断任务
47
+
42
48
  示例:
43
49
  agentbot init
44
50
  agentbot --profile ~/.agent-bot init --reset
@@ -61,10 +67,11 @@ Common commands:
61
67
  init --reset Reset and reconfigure an explicit profile
62
68
  console Open the local console
63
69
  server start Start Agent Bot in the background
64
- server status Show the server, Lark App ID, and safe-restart status
65
- server restart Schedule a safe restart
70
+ server status Show the server, Lark App ID, Agent processes, and safe-restart status
71
+ server restart [--task <task>] Schedule a safe restart and return status to the task's conversation
66
72
  server stop Stop Agent Bot
67
73
  task list List tasks
74
+ task current [--json] Show details for the task invoking the CLI
68
75
  task status <task> Show task status
69
76
  task chat <task> Show the task's Lark chat binding
70
77
  task prompt <task> <prompt> Send a Prompt to a task
@@ -87,6 +94,11 @@ Init options:
87
94
  --skip-feishu Initialize for Console only
88
95
  --reconfigure-feishu Replace only the existing Lark credentials
89
96
 
97
+ Restart options:
98
+ --task <task> Return safe-restart status to the task's conversation
99
+ --reason <reason> Set the restart reason
100
+ --immediate Restart immediately and possibly interrupt tasks
101
+
90
102
  Examples:
91
103
  agentbot init
92
104
  agentbot --profile ~/.agent-bot init --reset
@@ -29,16 +29,44 @@ export function formatServerStatus(data: unknown, language: CliLanguage = cliLan
29
29
  language,
30
30
  )
31
31
  : cliText("not scheduled", "未计划", language);
32
+ const agents = formatAgentProcesses(value.agents, language);
32
33
  return [
33
34
  `${cliText("Agent Bot server: ", "Agent Bot 服务:", language)}${state}`,
34
35
  `${cliText("Lark App ID: ", "飞书 App ID:", language)}${value.feishuAppId ?? cliText("not configured", "未配置", language)}`,
35
36
  `${cliText("PID: ", "PID:", language)}${value.pid ?? "-"}`,
36
37
  `${cliText("Started at: ", "启动时间:", language)}${value.startedAt ?? "-"}`,
37
38
  `${cliText("Supervisor: ", "Supervisor:", language)}${value.supervised ? cliText("enabled", "已启用", language) : cliText("disabled", "未启用", language)}`,
39
+ ...agents,
38
40
  `${cliText("Running tasks: ", "运行中任务:", language)}${activity.runningSessions ?? 0}`,
39
41
  `${cliText("Pending final deliveries: ", "待发送最终结果:", language)}${activity.pendingFinalDeliveries ?? 0}`,
40
42
  `${cliText("Safe restart: ", "安全重启:", language)}${safeRestart}`,
41
43
  "",
42
44
  ].join("\n");
43
45
  }
46
+
47
+ function formatAgentProcesses(value: unknown, language: CliLanguage): string[] {
48
+ const agents = Array.isArray(value)
49
+ ? value.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object")
50
+ : [];
51
+ if (agents.length === 0) {
52
+ return [`${cliText("Agents: ", "Agent 进程:", language)}-`];
53
+ }
54
+ return [
55
+ cliText("Agents:", "Agent 进程:", language),
56
+ ...agents.map((agent) => {
57
+ const name = typeof agent.name === "string" && agent.name.trim() ? agent.name.trim() : "-";
58
+ const title = typeof agent.title === "string" && agent.title.trim() ? agent.title.trim() : undefined;
59
+ const label = title && title !== name ? `${name} (${title})` : name;
60
+ const pid = typeof agent.pid === "number" && Number.isInteger(agent.pid) && agent.pid > 0
61
+ ? String(agent.pid)
62
+ : "-";
63
+ const version = typeof agent.version === "string" && agent.version.trim()
64
+ ? agent.version.trim()
65
+ : "-";
66
+ return language === "zh"
67
+ ? ` ${label}:PID ${pid},版本 ${version}`
68
+ : ` ${label}: PID ${pid}, version ${version}`;
69
+ }),
70
+ ];
71
+ }
44
72
  import { cliLanguage, cliText, type CliLanguage } from "./i18n.js";