@keyou007/agent-bot 0.1.13 → 0.1.14-alpha.1

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 (77) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +41 -44
  3. package/README.zh.md +41 -44
  4. package/config.example.yaml +4 -0
  5. package/dist/codex/CodexRuntime.js +8 -3
  6. package/dist/codex/CodexRuntime.js.map +1 -1
  7. package/dist/commands/CommandRouter.js +13 -0
  8. package/dist/commands/CommandRouter.js.map +1 -1
  9. package/dist/config/loadConfig.js +2 -0
  10. package/dist/config/loadConfig.js.map +1 -1
  11. package/dist/config/schema.js +2 -0
  12. package/dist/config/schema.js.map +1 -1
  13. package/dist/console/ConsoleTurnPresenter.js +2 -0
  14. package/dist/console/ConsoleTurnPresenter.js.map +1 -1
  15. package/dist/feishu/CardRenderer.js +428 -9
  16. package/dist/feishu/CardRenderer.js.map +1 -1
  17. package/dist/feishu/FeishuConnector.js +29 -8
  18. package/dist/feishu/FeishuConnector.js.map +1 -1
  19. package/dist/feishu/FeishuMessageClient.js +78 -8
  20. package/dist/feishu/FeishuMessageClient.js.map +1 -1
  21. package/dist/feishu/FeishuTurnPresenter.js +31 -2
  22. package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
  23. package/dist/feishu/ownerAccess.js +7 -0
  24. package/dist/feishu/ownerAccess.js.map +1 -0
  25. package/dist/index.js +14 -17
  26. package/dist/index.js.map +1 -1
  27. package/dist/presentation/OutboundRouter.js +19 -1
  28. package/dist/presentation/OutboundRouter.js.map +1 -1
  29. package/dist/presentation/splitMarkdown.js +16 -4
  30. package/dist/presentation/splitMarkdown.js.map +1 -1
  31. package/dist/proxy/ProxySessionController.js +1012 -58
  32. package/dist/proxy/ProxySessionController.js.map +1 -1
  33. package/dist/startup/StartupNotifier.js +65 -16
  34. package/dist/startup/StartupNotifier.js.map +1 -1
  35. package/dist/startup/startFeishu.js +2 -2
  36. package/dist/startup/startFeishu.js.map +1 -1
  37. package/dist/state/StateStore.js +250 -67
  38. package/dist/state/StateStore.js.map +1 -1
  39. package/dist/state/migrations.js +26 -0
  40. package/dist/state/migrations.js.map +1 -1
  41. package/dist/supervision/SafeRestartNotifier.js +17 -16
  42. package/dist/supervision/SafeRestartNotifier.js.map +1 -1
  43. package/dist/supervision/SafeRestartScheduler.js +12 -7
  44. package/dist/supervision/SafeRestartScheduler.js.map +1 -1
  45. package/dist/supervision/replacementSupervisor.js +45 -4
  46. package/dist/supervision/replacementSupervisor.js.map +1 -1
  47. package/dist/supervisor.js +30 -18
  48. package/dist/supervisor.js.map +1 -1
  49. package/docs/technical-reference.md +18 -5
  50. package/docs/technical-reference.zh.md +18 -5
  51. package/npm-shrinkwrap.json +2 -2
  52. package/package.json +1 -1
  53. package/skills/agent-bot/SKILL.md +8 -2
  54. package/src/codex/CodexRuntime.ts +9 -3
  55. package/src/commands/CommandRouter.ts +13 -0
  56. package/src/commands/commandTypes.ts +2 -0
  57. package/src/config/loadConfig.ts +2 -0
  58. package/src/config/schema.ts +2 -0
  59. package/src/console/ConsoleTurnPresenter.ts +4 -1
  60. package/src/feishu/CardRenderer.ts +533 -9
  61. package/src/feishu/FeishuConnector.ts +44 -10
  62. package/src/feishu/FeishuMessageClient.ts +118 -7
  63. package/src/feishu/FeishuTurnPresenter.ts +42 -3
  64. package/src/feishu/ownerAccess.ts +7 -0
  65. package/src/feishu/types.ts +7 -0
  66. package/src/index.ts +20 -20
  67. package/src/presentation/OutboundRouter.ts +28 -3
  68. package/src/presentation/splitMarkdown.ts +26 -3
  69. package/src/proxy/ProxySessionController.ts +1177 -57
  70. package/src/startup/StartupNotifier.ts +67 -17
  71. package/src/startup/startFeishu.ts +5 -3
  72. package/src/state/StateStore.ts +394 -65
  73. package/src/state/migrations.ts +26 -0
  74. package/src/supervision/SafeRestartNotifier.ts +17 -17
  75. package/src/supervision/SafeRestartScheduler.ts +13 -6
  76. package/src/supervision/replacementSupervisor.ts +50 -4
  77. package/src/supervisor.ts +35 -16
@@ -140,7 +140,9 @@ feishu:
140
140
  appId: "${FEISHU_APP_ID}"
141
141
  appSecret: "${FEISHU_APP_SECRET}"
142
142
  userOpenId: "${FEISHU_USER_OPEN_ID}"
143
+ respondToOwnerOnly: true
143
144
  respondToAllGroupMessages: true
145
+ thinkingCardLayout: "grouped"
144
146
 
145
147
  console:
146
148
  enabled: true
@@ -172,9 +174,15 @@ logging:
172
174
  path: "./logs/agent-bot.log"
173
175
  ```
174
176
 
175
- `feishu.respondToAllGroupMessages` defaults to `true`. Set it to `false` to ignore group messages unless they mention the current bot. The worker resolves the bot's Open ID at startup so mentioning another member does not trigger it. Private messages are always accepted. Initialization requests all-user group-message delivery regardless of this runtime option, allowing it to be changed later without another authorization.
177
+ `feishu.respondToOwnerOnly` defaults to `true`. It compares each message sender and card-action operator Open ID with `feishu.userOpenId`, ignoring non-owner input before durable event claims, reactions, downloads, commands, or Agent work. Set it to `false` to allow other users. If it is enabled without a configured owner Open ID, all Feishu messages and card actions are ignored; configure `FEISHU_USER_OPEN_ID` or temporarily disable the restriction to bootstrap from a private message.
176
178
 
177
- `agentbot server start` requires both Feishu credentials. The worker starts the SDK's persistent connection without inspecting SDK logs or private connection state, then sends startup status cards as an outbound readiness check. Each startup card includes the Agent Bot version read from the installed package metadata. Every startup sends a card to every known private chat, regardless of recent activity, plus each non-topic group active during the minute before this Worker started. After a safe restart, it also sends to every group enrolled in that restart's notification set regardless of activity age. A topic route is normalized to its parent group, so topic contexts are never startup-card targets. Safe-restart progress cards have a narrower audience: only conversations that explicitly requested the current restart receive and update them; recently active chats are not enrolled automatically. If the database has no eligible chat yet, it sends the startup card to `feishu.userOpenId` using an `open_id` private message. When startup notification targets exist, at least one card must be delivered before the server reports ready; individual target failures remain isolated. If neither a known chat nor `feishu.userOpenId` is available, startup continues without the outbound check. The first subsequent private message can persist the missing user Open ID for later startup notifications and CLI-created groups. Missing credentials still fail startup with an initialization hint. `agentbot console` is the explicit local-only path and does not require Feishu credentials.
179
+ `feishu.respondToAllGroupMessages` defaults to `true`. Set it to `false` to additionally ignore owner group messages unless they mention the current bot. The worker resolves the bot's Open ID at startup so mentioning another member does not trigger it. Owner private messages are unaffected. Initialization requests all-user group-message delivery regardless of this runtime option, allowing it to be changed later without another authorization.
180
+
181
+ `/mute` and `/mute on` persist mention-only response mode for the current base group; `/mute off` disables it. The group timeline and every topic share this state. While muted, messages that do not mention the current bot are ignored before event claims, reactions, activity tracking, image downloads, command parsing, or Agent calls. The command that disables mute must itself mention the bot. Private chats do not support `/mute`.
182
+
183
+ `feishu.thinkingCardLayout` defaults to `grouped`. The grouped renderer keeps Commentary and user steering messages visible, uses Commentary as the only execution-group boundary, uses only the latest native reasoning as each group title, and keeps every tool available inside the group. Every execution panel is rendered with a collapsed default and a stable `element_id`; Agent Bot never changes the expanded flag based on tool status, user steering, or later Commentary, allowing the Feishu client to retain a user's manual expansion across full-card updates. Grouped pagination first renders complete tool panels, then fills pages from the newest end according to the rendered UTF-8 JSON size and component count. The activity area is limited to 24KB and 160 components, leaving headroom under Feishu's recommended 30KB card size and 200-component hard limit for the header, plan, files, and actions. Execution groups above eight tools are split into stable subpanels, but every tool keeps its expandable command, result, and image content. History pages are measured separately with complete native reasoning included. Set the option to `timeline` to use the unchanged original renderer and its 40-activity pages while the grouped layout is being refined.
184
+
185
+ `agentbot server start` requires both Feishu credentials. The worker starts the SDK's persistent connection without inspecting SDK logs or private connection state, then sends startup status cards as an outbound readiness check. Each startup card includes the Agent Bot version read from the installed package metadata. Every startup sends a card to every known private chat, regardless of recent activity, plus each non-topic group active during the minute before this Worker started. After a safe restart, it also sends to every conversation enrolled in that restart's notification set regardless of activity age. Topic routes retain their originating message anchor across the replacement Supervisor and receive the startup card as a reply in the original topic instead of creating a new root topic in the parent group. Safe-restart progress cards have a narrower audience: only conversations that explicitly requested the current restart receive and update them; recently active chats are not enrolled automatically. If the database has no eligible chat yet, it sends the startup card to `feishu.userOpenId` using an `open_id` private message. When startup notification targets exist, at least one card must be delivered before the server reports ready; individual target failures remain isolated. If neither a known chat nor `feishu.userOpenId` is available, startup continues without the outbound check. With `respondToOwnerOnly: false`, the first subsequent private message can persist the missing user Open ID for later startup notifications and CLI-created groups; the default owner-only mode requires the owner Open ID to be configured first. Missing credentials still fail startup with an initialization hint. `agentbot console` is the explicit local-only path and does not require Feishu credentials.
178
186
 
179
187
  At least one agent must be configured. `defaults.agent` must name a configured agent.
180
188
 
@@ -229,7 +237,7 @@ The new group's welcome message reports the persisted fork settings: Provider, m
229
237
  - 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`.
230
238
  - Unknown slash commands are rejected and never forwarded as Prompts.
231
239
 
232
- 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.
240
+ 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. Grouped thinking cards derive execution groups only while rendering; the persisted chronological activity stream remains layout-independent, so changing layouts or restarting a Worker does not discard reasoning or tool history.
233
241
 
234
242
  Successfully completed progress cards include a `Reset` action with its compact effect warning directly below the action. `/turns` places the same effect warning at the top, then presents the current task's completed-turn snapshots in reverse chronological order, 10 per page. Each `turn_started` event persists the previous completed turn as its parent. Existing tasks are backfilled from snapshot timestamps and `session_reset_to_turn` audits, so the first turn after a historical Reset points to the selected turn instead of the time-adjacent abandoned branch. The renderer computes lanes across the complete history before slicing a page, then displays true continuations and merges in the graph column, indented content in the second column, and state or action in the third. The current conversation point is marked instead of showing an action; every other turn has a `Reset` action. Pagination and successful Reset actions update the same card, remain bound to the task that opened it, and move the current marker to the selected turn. The success text identifies the target with its Prompt summary, completion time, and Turn ID. Agent Bot records the App Server thread that originally owned each turn, forks that thread through the selected turn, and replaces the current task's remote thread binding without changing its local task ID, title, Agent, project directory, execution settings, or chat routing. Existing snapshots after the selected turn are not deleted, so the card shows retained turns from the old path together with turns later completed on the new branch. Reset is rejected while the current task is active, and it never reverts local file changes.
235
243
 
@@ -271,10 +279,15 @@ SQLite stores:
271
279
  - Local and App Server task/thread identifiers
272
280
  - Model, reasoning effort, and permission mode
273
281
  - Queued Prompts
282
+ - Accepted/running turn attempts, including the original route, Prompt, images, and message anchor
274
283
  - Progress snapshots and message bindings
275
284
  - Final-message delivery records
276
285
 
277
- On startup, Agent Bot reconciles persisted active work with its owning App Server through `thread/read`. It also reconciles when turn identifiers change, terminal notifications arrive, or control requests fail.
286
+ On every Worker startup, Agent Bot scans nonterminal turn attempts before releasing persisted Prompt queues. Only attempts with activity during the preceding five minutes are eligible; runtime events and a one-minute heartbeat refresh the durable activity timestamp while a turn remains active. Older unfinished attempts are marked interrupted without a recovery notification or continuation. Eligible recovery first notifies the original private chat, group, or topic. A remotely completed turn is synchronized and only its pending final delivery is resumed. A remotely active turn is reattached to a new progress card and polled until terminal. A stale App Server turn or an interrupted ACP process is continued in the same task and workspace with a new turn and a new progress card; the continuation Prompt tells the Agent to inspect existing effects before repeating work. Recovery retries remain durable across repeated restarts.
287
+
288
+ When a turn ends with a transient LLM-service failure, Agent Bot automatically starts up to three additional turns in the same task. Rate limits, temporary overload, upstream 5xx responses, timeouts, and interrupted provider streams are retryable. Authentication, quota exhaustion, context limits, invalid requests, unsupported models, permissions, policy failures, and tool errors are terminal immediately. Every retry gets a fresh progress card and a Prompt that warns the Agent to inspect existing effects before repeating work. The retry count and pending message bindings are persisted; original and steering-message reactions remain pending and follow the newest retry turn until it succeeds or exhausts the retry budget.
289
+
290
+ Agent Bot also reconciles when turn identifiers change, terminal notifications arrive, or control requests fail. Stale persisted `inProgress` turns are not considered active unless the App Server thread itself reports an active status.
278
291
 
279
292
  The final-delivery ledger prevents duplicate successful replies. App Server requests use finite timeouts so a stalled request cannot permanently occupy a message route. Session lifecycle requests allow 60 seconds because compatible third-party Agents may need more than 30 seconds for `thread/start`; control requests remain shorter.
280
293
 
@@ -301,7 +314,7 @@ A safe restart waits for:
301
314
 
302
315
  The Feishu `/restart` command uses this safe path by default; `/restart --force` restarts immediately and may interrupt active work. The command rejects every other argument. New messages reset the quiet timer. CLI `--immediate` and `--force` also skip these checks. Exit code `75` identifies an intentional worker restart.
303
316
 
304
- A pending safe-restart schedule accumulates the exact notification route of every conversation that triggers it. Every inbound user message, including slash commands, marks its base private or group conversation active. On every status update the notifier also enrolls every persisted conversation active during the preceding minute. Each enrolled route receives its own status card and final restarting acknowledgement and remains enrolled until the schedule ends, even after its activity becomes older than one minute. Repeated routes are deduplicated; when a topic route was first discovered without a message anchor, a later anchored trigger upgrades it. Topic requests retain the originating message ID and use a thread reply so restart updates cannot fall back to the group body. Recent topic activity is tracked on the parent group, while an explicit topic trigger remains in that topic. Later requests also update the restart reason. Before replacement, Agent Bot extracts and deduplicates the parent group of every enrolled route, passes that list through the replacement Supervisor, and adds those groups to the service-wide startup-card targets. The Supervisor retains the list until the Worker has remained stable for 60 seconds, so an early crash-driven relaunch does not lose required recipients.
317
+ A pending safe-restart schedule accumulates the exact notification route of every conversation that triggers it. Every inbound user message, including slash commands, marks its base private or group conversation active. Each enrolled route receives its own status card and final restarting acknowledgement and remains enrolled until the schedule ends. Repeated routes are deduplicated; when a topic route was first discovered without a message anchor, a later anchored trigger upgrades it. Topic requests retain the originating message ID and use a thread reply so restart updates cannot fall back to the group body. Each route also retains the reason supplied by that conversation, so a later request elsewhere cannot overwrite an earlier card with an unrelated reason. Before replacement, Agent Bot passes every complete route, including topic message anchors, through the replacement Supervisor and uses the same route for the post-restart startup card. The Supervisor retains the routes until the Worker has remained stable for 60 seconds, so an early crash-driven relaunch does not lose required recipients.
305
318
 
306
319
  Use `agentbot server restart --task <task>` when a CLI request belongs to a specific task. Task numbers, full IDs, and unambiguous ID prefixes are resolved before the control request is sent. Without `--task`, the Server infers the safe-restart status route only when all running tasks belong to one conversation; it rejects an ambiguous request spanning multiple running conversations. A CLI restart with no running task has no conversation owner for its waiting status.
307
320
 
@@ -140,7 +140,9 @@ feishu:
140
140
  appId: "${FEISHU_APP_ID}"
141
141
  appSecret: "${FEISHU_APP_SECRET}"
142
142
  userOpenId: "${FEISHU_USER_OPEN_ID}"
143
+ respondToOwnerOnly: true
143
144
  respondToAllGroupMessages: true
145
+ thinkingCardLayout: "grouped"
144
146
 
145
147
  console:
146
148
  enabled: true
@@ -172,9 +174,15 @@ logging:
172
174
  path: "./logs/agent-bot.log"
173
175
  ```
174
176
 
175
- `feishu.respondToAllGroupMessages` 默认为 `true`。设为 `false` 后,未 @ 当前机器人的群消息会被忽略;Worker 启动时会解析机器人的 Open ID,因此 @ 其他成员不会误触发。私聊始终正常处理。无论该运行时配置如何,初始化都会申请接收全部群消息的权限,之后切换配置无需再次授权。
177
+ `feishu.respondToOwnerOnly` 默认为 `true`。每条消息的发送者及卡片操作人的 Open ID 都会与 `feishu.userOpenId` 比较;非拥有者输入会在持久化事件、添加 reaction、下载图片、执行命令或启动 Agent 前被忽略。设为 `false` 可允许其他用户。开启后若未配置拥有者 Open ID,所有飞书消息和卡片操作都会被忽略;需先配置 `FEISHU_USER_OPEN_ID`,或临时关闭限制后通过私聊完成补全。
176
178
 
177
- `agentbot server start` 要求同时配置飞书 `appId` 和 `appSecret`。Worker 启动 SDK 长连接时不读取 SDK 日志或私有连接状态,随后通过发送启动状态卡片检查出站能力。每张启动卡片都会显示从已安装包元数据读取的 Agent Bot 版本。每次启动都会向所有已知私聊发送卡片,不受最近活跃时间影响;同时向本次 Worker 启动前 1 分钟内活跃的非话题群聊发送卡片。安全重启后,还会向所有已加入本次重启通知范围的群发送,不受活跃时间限制;话题路由会折算为所属普通群,因此话题会话永远不会成为启动卡目标。安全重启进度卡的范围更窄:只有明确触发本次安全重启的会话会收到并持续更新,最近活跃会话不会被自动加入。数据库里尚无符合条件的会话时,启动卡改用 `feishu.userOpenId`,按 `open_id` 私聊发送。存在启动通知目标时,至少一张卡片发送成功后 Server 才报告就绪,单个目标发送失败仍相互隔离。如果既没有已知会话,也没有 `feishu.userOpenId`,启动会跳过出站检查并继续;之后收到的第一条私聊消息可以补全用户 Open ID,供后续启动通知和 CLI 建群使用。缺少凭据时仍会启动失败并提示先初始化。`agentbot console` 是明确的纯本地入口,不需要飞书凭据。
179
+ `feishu.respondToAllGroupMessages` 默认为 `true`。设为 `false` 后,拥有者未 @ 当前机器人的群消息也会被忽略;Worker 启动时会解析机器人的 Open ID,因此 @ 其他成员不会误触发。拥有者私聊不受影响。无论该运行时配置如何,初始化都会申请接收全部群消息的权限,之后切换配置无需再次授权。
180
+
181
+ `/mute` 或 `/mute on` 会为当前基础群聊持久化仅 @ 响应模式,`/mute off` 会关闭。群正文及其所有话题共享该状态;静音期间未 @ 当前机器人的消息会在事件去重、reaction、活跃时间、图片下载、命令解析和 Agent 调用之前被忽略。关闭静音的命令本身也必须 @ 机器人。私聊不支持 `/mute`。
182
+
183
+ `feishu.thinkingCardLayout` 默认为 `grouped`。分组渲染器保持 Commentary 和用户追加消息可见,只把 Commentary 作为执行组边界;每个执行组只用最新原生思考作为标题,展开后仍可查看组内全部工具。所有执行面板都以折叠状态作为默认值,并使用稳定的 `element_id`;Agent Bot 不再根据工具状态、用户追加或后续 Commentary 改写展开属性,让飞书客户端能够在整卡更新时保留用户手动展开的状态。分组分页会先完整渲染工具面板,再根据生成内容的 UTF-8 JSON 大小和组件数量从最新端向前装页。活动区上限为 24KB 和 160 个组件,为飞书建议的 30KB 卡片大小及 200 个组件硬限制预留标题、计划、文件变更和操作按钮空间。超过 8 个工具的单个执行组会拆成标识稳定的子面板,但每个工具都继续保留可展开的命令、结果和图片内容。历史页会包含完整原生思考并独立测量。新版布局完善期间,可将该配置设为 `timeline` 使用保持不变的原版渲染器及其每页 40 条活动规则。
184
+
185
+ `agentbot server start` 要求同时配置飞书 `appId` 和 `appSecret`。Worker 启动 SDK 长连接时不读取 SDK 日志或私有连接状态,随后通过发送启动状态卡片检查出站能力。每张启动卡片都会显示从已安装包元数据读取的 Agent Bot 版本。每次启动都会向所有已知私聊发送卡片,不受最近活跃时间影响;同时向本次 Worker 启动前 1 分钟内活跃的非话题群聊发送卡片。安全重启后,还会向所有已加入本次重启通知范围的会话发送,不受活跃时间限制;话题路由及其原始消息锚点会完整经过替换 Supervisor,启动卡以回复形式返回原话题,不会在父群创建新的根话题。安全重启进度卡的范围更窄:只有明确触发本次安全重启的会话会收到并持续更新,最近活跃会话不会被自动加入。数据库里尚无符合条件的会话时,启动卡改用 `feishu.userOpenId`,按 `open_id` 私聊发送。存在启动通知目标时,至少一张卡片发送成功后 Server 才报告就绪,单个目标发送失败仍相互隔离。如果既没有已知会话,也没有 `feishu.userOpenId`,启动会跳过出站检查并继续;仅当 `respondToOwnerOnly: false` 时,之后收到的第一条私聊消息可以补全用户 Open ID,供后续启动通知和 CLI 建群使用。默认的仅拥有者模式要求先配置拥有者 Open ID。缺少凭据时仍会启动失败并提示先初始化。`agentbot console` 是明确的纯本地入口,不需要飞书凭据。
178
186
 
179
187
  必须至少配置一个 Agent,`defaults.agent` 必须指向已配置的 Agent。
180
188
 
@@ -229,7 +237,7 @@ Agent Bot 会在每个 `thread/fork` 请求中默认发送实验性的 `excludeT
229
237
  - 完整斜杠命令名优先;否则在解析参数前展开唯一的命令名前缀或已登记的复合命令首字母缩写,匹配多个命令时会拒绝并列出候选命令。当前缩写为 `fg` → `forkgroup`、`ng` → `newgroup`、`ns` → `nosteer`
230
238
  - 未知斜杠命令会被拒绝,不会作为 Prompt 转发
231
239
 
232
- 每个 turn 只有一张进度卡。普通更新最多每两秒一次,关键更新最短间隔 500 毫秒。完成时先把进度卡更新为终态,再单独发送 Markdown 最终回答。
240
+ 每个 turn 只有一张进度卡。普通更新最多每两秒一次,关键更新最短间隔 500 毫秒。完成时先把进度卡更新为终态,再单独发送 Markdown 最终回答。分组思考卡片只在渲染时派生执行组,持久化的时间线活动不依赖布局,因此切换布局或 Worker 重启都不会丢失思考和工具历史。
233
241
 
234
242
  成功完成的思考卡片包含 `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,本地文件修改也不会被回退。
235
243
 
@@ -271,10 +279,15 @@ SQLite 保存:
271
279
  - 本地任务标识与 App Server task/thread 标识
272
280
  - 模型、思考强度和权限模式
273
281
  - 排队 Prompt
282
+ - 已接受/运行中的 turn 执行记录,包括原会话路由、Prompt、图片和消息锚点
274
283
  - 进度快照和消息绑定
275
284
  - 最终消息投递记录
276
285
 
277
- 启动时,Agent Bot 通过 `thread/read` 将持久化的活动工作与其所属 App Server 状态校准。Turn 标识变化、收到终态通知或控制请求失败时也会重新校准。
286
+ 每次 Worker 启动时,Agent Bot 都会先扫描未终结的 turn 执行记录,再放行持久化的 Prompt 队列。只有前 5 分钟内仍有活动的执行才具备恢复资格;turn 运行期间的运行时事件和每分钟心跳会刷新持久化的活动时间。更早的未完成执行会被标记为已中断,不发送恢复通知,也不创建续跑 turn。符合条件的恢复会先通知原私聊、群聊或话题;远端已完成的 turn 只同步状态并继续未完成的最终消息投递;远端仍活动的 turn 会换用新的进度卡并持续轮询到终态;已失效的 App Server turn 或被中断的 ACP 进程会在同一任务和工作目录中创建新的续跑 turn,续跑 Prompt 会要求 Agent 先检查已有副作用,避免重复操作。恢复状态在连续多次重启之间保持持久化。
287
+
288
+ 当 turn 因临时 LLM 服务错误失败时,Agent Bot 会在同一任务中自动再创建最多 3 个 turn。限流、临时过载、上游 5xx、超时和模型服务流中断可以重试;认证失败、额度耗尽、上下文超限、无效请求、不支持的模型、权限或策略错误以及工具错误会立即终止。每次重试使用新的思考卡片,重试 Prompt 会要求 Agent 先检查已有副作用再继续,避免重复操作。重试次数和待处理消息绑定会持久化;原始消息及追加消息的 Reaction 会保持处理中,并跟随最新的重试 turn,直到成功或耗尽重试次数。
289
+
290
+ Turn 标识变化、收到终态通知或控制请求失败时也会重新校准。除非 App Server thread 自身报告活动状态,否则持久化的陈旧 `inProgress` turn 不会被视为仍在运行。
278
291
 
279
292
  最终消息投递账本用于避免重复发送成功回答。App Server 请求使用有限超时,避免阻塞的请求永久占用消息路由。会话生命周期请求允许 60 秒,因为兼容的第三方 Agent 完成 `thread/start` 可能需要超过 30 秒;控制请求仍使用更短的超时。
280
293
 
@@ -301,7 +314,7 @@ Supervisor、Worker、替换 Supervisor 和 Console Worker 默认启用 Node fat
301
314
 
302
315
  飞书 `/restart` 命令默认使用这条安全重启路径;`/restart --force` 会立即重启,并可能中断正在执行的任务。该命令拒绝其他所有参数。新消息会重置静默计时,CLI 的 `--immediate` 和 `--force` 也会跳过这些检查。退出码 `75` 表示主动 worker 重启。
303
316
 
304
- 待执行的安全重启计划会收集每一个触发它的精确会话路由。收到任何用户消息时,包括斜杠命令,都会把所属基础私聊或群聊标为活跃;每次状态更新还会加入此前 1 分钟内活跃的所有已知会话。每个已加入的会话都会收到各自的状态卡片和真正开始重启前的提示,而且即使之后超过 1 分钟未活跃,也会一直保留到本次计划结束。同一路由会去重;如果话题路由最初缺少消息锚点,后续带锚点的触发会补全它。话题请求会保留原消息 ID,并通过话题回复发送,避免重启状态落到群主会话;话题中的最近活动记在所属普通群,只有显式从话题触发时才向该话题本身发送。后续请求也会更新重启原因。替换进程前,Agent Bot 会提取并去重所有已加入路由所属的普通群,经替换 Supervisor 传给 Worker,再把这些群加入服务级启动卡目标。Supervisor 会保留该列表,直到 Worker 连续稳定运行 60 秒,避免启动阶段异常退出并重新拉起时丢失必发群。
317
+ 待执行的安全重启计划会收集每一个触发它的精确会话路由。收到任何用户消息时,包括斜杠命令,都会把所属基础私聊或群聊标为活跃。每个已加入的会话都会收到各自的状态卡片和真正开始重启前的提示,并一直保留到本次计划结束。同一路由会去重;如果话题路由最初缺少消息锚点,后续带锚点的触发会补全它。话题请求会保留原消息 ID,并通过话题回复发送,避免重启状态落到群主会话。每条路由还会保留该会话自己的重启原因,其他会话的后续请求不会用无关原因覆盖旧卡片。替换进程前,Agent Bot 会把包含话题消息锚点在内的完整路由经替换 Supervisor 传给 Worker,并用同一路由发送重启后的启动卡。Supervisor 会保留这些路由,直到 Worker 连续稳定运行 60 秒,避免启动阶段异常退出并重新拉起时丢失必发会话。
305
318
 
306
319
  当 CLI 重启属于某个具体任务时,使用 `agentbot server restart --task <任务>`。任务序号、完整 ID 和不冲突的 ID 前缀会在发送控制请求前解析。未传 `--task` 时,只有所有运行中任务都属于同一个会话,Server 才会自动推断安全重启状态的发送目标;如果同时有多个运行中的会话,请求会被拒绝。没有运行中任务的本地 CLI 重启没有等待状态所属会话。
307
320
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@keyou007/agent-bot",
3
- "version": "0.1.13",
3
+ "version": "0.1.14-alpha.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@keyou007/agent-bot",
9
- "version": "0.1.13",
9
+ "version": "0.1.14-alpha.1",
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",
3
+ "version": "0.1.14-alpha.1",
4
4
  "type": "module",
5
5
  "description": "Use Codex, TraeX, and compatible coding agents from Lark/Feishu.",
6
6
  "license": "GPL-3.0-only",
@@ -71,7 +71,7 @@ Rerun `agentbot init` after upgrading Agent Bot. It non-destructively upgrades a
71
71
 
72
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.
73
73
 
74
- 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.
74
+ Initialization always requests all-user group-message delivery. The runtime option `feishu.respondToOwnerOnly` defaults to `true` and accepts only messages and card actions whose user Open ID matches `feishu.userOpenId`; all other users are ignored before reactions or task processing. Set it to `false` only when the owner wants collaborators to use the bot. With owner-only mode enabled, a missing `FEISHU_USER_OPEN_ID` blocks all Feishu user input until the owner is configured. The separate `feishu.respondToAllGroupMessages` option defaults to `true`; setting it to `false` additionally requires the owner to mention the current bot in groups. Owner private messages are unaffected.
75
75
 
76
76
  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`.
77
77
 
@@ -217,18 +217,24 @@ Give every created group a deterministic scheme-C Identicon avatar. Display the
217
217
 
218
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.
219
219
 
220
+ Topics created from a persisted Agent Bot user message, thinking card, or final response automatically fork that source turn on first use. A topic whose root or parent cannot be mapped to a persisted Agent Bot turn starts independently, including topics that quote ordinary messages, command replies, or error messages: its first ordinary Prompt creates a fresh task, `/new` explicitly creates one, and `/status` reports the empty topic without requiring a fork anchor. Explicit `/forkgroup` still requires a valid source turn when the topic has no bound task.
221
+
220
222
  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.
221
223
 
222
224
  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.
223
225
 
224
226
  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.
225
227
 
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.
228
+ Use `/dir [path]` in Feishu to open the local file browser with 15 entries per page. Without a path it starts in the current task directory; resolve relative paths from that directory and `~` from the operating-system user's home directory. Directory names navigate within the same card, while file names upload and send the selected file to the current conversation. On Windows, selecting `..` at a drive root opens the virtual drive list. Every visible directory, including the current directory, has `New` and `NewGroup` actions: `New` behaves like `/new --dir <selected-directory>`, while `NewGroup` behaves like `/newgroup --dir <selected-directory>`, including Agent and execution-setting inheritance. `Previous` and `Next` update the existing card instead of sending another browser card.
229
+
230
+ The Feishu `/sessions` card is titled `任务列表`, groups tasks by project, and shows ten compact, collapsed task rows per page. Each project row has `New` and `NewGroup`; they inherit the current, active, or most recent task in that project and create work in the same project directory. Click a task row to expand its status, Agent, task ID, directory, and task-level actions. `Previous` and `Next` update the same card; they never expand earlier pages into one long card. `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.
227
231
 
228
232
  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.
229
233
 
230
234
  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
235
 
236
+ Feishu thinking cards use `feishu.thinkingCardLayout: grouped` by default. The grouped layout keeps Commentary and user steering visible and groups native reasoning and tools until the next Commentary. Execution panels default to collapsed and retain stable component IDs so Feishu can preserve a user's manual expansion across updates. Long turns paginate complete rendered tool panels from the newest end according to their UTF-8 JSON size and component count, not fixed message or tool counts; grouped history retains all stored native reasoning, commands, results, and images. Use `timeline` only as a temporary rollback to the unchanged original layout and its 40-activity pages.
237
+
232
238
  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.
233
239
 
234
240
  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.
@@ -770,11 +770,17 @@ export class CodexRuntime implements AgentRuntime {
770
770
  const activeTurnId = session.activeTurnId;
771
771
 
772
772
  if (!activeTurnId) {
773
- if (latestInProgress) this.adoptTurn(session, latestInProgress.id, turnStartedAt(latestInProgress));
773
+ if (latestInProgress && thread.status?.type === "active") {
774
+ this.adoptTurn(session, latestInProgress.id, turnStartedAt(latestInProgress));
775
+ }
774
776
  return;
775
777
  }
776
778
 
777
- if (latestInProgress?.id === activeTurnId) return;
779
+ if (latestInProgress?.id === activeTurnId) {
780
+ if (thread.status?.type === "active") return;
781
+ this.supersedeTurn(session, activeTurnId);
782
+ return;
783
+ }
778
784
  if (latestInProgress) {
779
785
  this.supersedeTurn(session, activeTurnId);
780
786
  this.adoptTurn(session, latestInProgress.id, turnStartedAt(latestInProgress));
@@ -1091,7 +1097,7 @@ function remoteSessionSummary(thread: CodexThreadSnapshot): RemoteSessionSummary
1091
1097
  // that owned it. Only the owning app-server's active thread status (or the
1092
1098
  // rollout activity detector applied below) is evidence that it is still live.
1093
1099
  const lastTurnStatus = lastTurn?.status === "inProgress" && status !== "active"
1094
- ? undefined
1100
+ ? "interrupted"
1095
1101
  : lastTurn?.status;
1096
1102
  const lastText = [...(lastTurn?.items ?? [])]
1097
1103
  .reverse()
@@ -2,11 +2,13 @@ import type { Command } from "./commandTypes.js";
2
2
 
3
3
  const COMMAND_NAMES = [
4
4
  "agent",
5
+ "dir",
5
6
  "fork",
6
7
  "forkgroup",
7
8
  "goal",
8
9
  "help",
9
10
  "model",
11
+ "mute",
10
12
  "new",
11
13
  "newgroup",
12
14
  "nosteer",
@@ -49,6 +51,8 @@ export class CommandRouter {
49
51
  const command = resolveCommandName(rawCommand);
50
52
 
51
53
  switch (command) {
54
+ case "dir":
55
+ return { type: "dir", directory: args.join(" ").trim() || undefined };
52
56
  case "new":
53
57
  return parseNewCommand(args);
54
58
  case "newgroup":
@@ -94,6 +98,15 @@ export class CommandRouter {
94
98
  if (args.length === 0) return { type: "restart" };
95
99
  if (args.length === 1 && args[0] === "--force") return { type: "restart", force: true };
96
100
  throw new Error("/restart 只接受一个可选的 --force 参数。");
101
+ case "mute": {
102
+ if (args.length === 0 || (args.length === 1 && args[0]!.toLowerCase() === "on")) {
103
+ return { type: "mute", enabled: true };
104
+ }
105
+ if (args.length === 1 && args[0]!.toLowerCase() === "off") {
106
+ return { type: "mute", enabled: false };
107
+ }
108
+ throw new Error("/mute 只接受 on 或 off;不传参数等同于 /mute on。");
109
+ }
97
110
  case "turns":
98
111
  if (args.length > 0) throw new Error("/turns 不接受参数,请在历史轮次卡片中选择 turn。");
99
112
  return { type: "turns" };
@@ -2,6 +2,7 @@ export type Command =
2
2
  | { type: "shell"; command: string }
3
3
  | { type: "new"; title?: string; cwd?: string; projectless?: boolean }
4
4
  | { type: "newgroup"; title?: string; cwd?: string; projectless?: boolean }
5
+ | { type: "dir"; directory?: string }
5
6
  | { type: "forkgroup"; title?: string }
6
7
  | { type: "fork"; sessionId?: string }
7
8
  | { type: "title"; title: string }
@@ -15,6 +16,7 @@ export type Command =
15
16
  | { type: "goal"; action: "set" | "edit"; objective: string }
16
17
  | { type: "goal"; action: "pause" | "resume" | "clear" }
17
18
  | { type: "restart"; force?: boolean }
19
+ | { type: "mute"; enabled: boolean }
18
20
  | { type: "turns" }
19
21
  | { type: "model" }
20
22
  | { type: "provider" }
@@ -15,7 +15,9 @@ const DEFAULT_CONFIG = `feishu:
15
15
  appId: "\${FEISHU_APP_ID}"
16
16
  appSecret: "\${FEISHU_APP_SECRET}"
17
17
  userOpenId: "\${FEISHU_USER_OPEN_ID}"
18
+ respondToOwnerOnly: true
18
19
  respondToAllGroupMessages: true
20
+ thinkingCardLayout: "grouped"
19
21
 
20
22
  console:
21
23
  enabled: true
@@ -28,7 +28,9 @@ export const appConfigSchema = z.object({
28
28
  appId: z.string().optional(),
29
29
  appSecret: z.string().optional(),
30
30
  userOpenId: z.string().optional(),
31
+ respondToOwnerOnly: z.boolean().default(true),
31
32
  respondToAllGroupMessages: z.boolean().default(true),
33
+ thinkingCardLayout: z.enum(["grouped", "timeline"]).default("grouped"),
32
34
  useConsoleWhenMissingCredentials: z.boolean().default(false),
33
35
  }),
34
36
  agents: z.record(z.string().min(1), agentConfigSchema).refine(
@@ -27,8 +27,9 @@ export class ConsoleTurnPresenter implements TurnPresenter {
27
27
  this.agentLabels.delete(sessionId);
28
28
  }
29
29
 
30
- async startPendingTurn(sessionId: string, contextKey: string): Promise<void> {
30
+ async startPendingTurn(sessionId: string, contextKey: string): Promise<string | undefined> {
31
31
  await this.outbound.sendText(contextKey, `Connecting to ${this.agentLabel(sessionId)}...`);
32
+ return undefined;
32
33
  }
33
34
 
34
35
  async failPendingTurn(sessionId: string, message: string): Promise<void> {
@@ -36,6 +37,8 @@ export class ConsoleTurnPresenter implements TurnPresenter {
36
37
  if (contextKey) await this.outbound.sendText(contextKey, `${this.agentLabel(sessionId)} failed to start: ${message}`);
37
38
  }
38
39
 
40
+ async interruptTurnForRecovery(): Promise<void> {}
41
+
39
42
  async appendSteerMessage(): Promise<void> {}
40
43
 
41
44
  async onEvent(event: AgentEvent): Promise<void> {