opencode-bridge 2.9.0-beta

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 (237) hide show
  1. package/.env.example +131 -0
  2. package/LICENSE +674 -0
  3. package/README.md +1195 -0
  4. package/bin/opencode-bridge.js +31 -0
  5. package/dist/commands/effort.d.ts +9 -0
  6. package/dist/commands/effort.d.ts.map +1 -0
  7. package/dist/commands/effort.js +56 -0
  8. package/dist/commands/effort.js.map +1 -0
  9. package/dist/commands/parser.d.ts +37 -0
  10. package/dist/commands/parser.d.ts.map +1 -0
  11. package/dist/commands/parser.js +355 -0
  12. package/dist/commands/parser.js.map +1 -0
  13. package/dist/config.d.ts +91 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +340 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/feishu/cards-stream.d.ts +65 -0
  18. package/dist/feishu/cards-stream.d.ts.map +1 -0
  19. package/dist/feishu/cards-stream.js +448 -0
  20. package/dist/feishu/cards-stream.js.map +1 -0
  21. package/dist/feishu/cards.d.ts +81 -0
  22. package/dist/feishu/cards.d.ts.map +1 -0
  23. package/dist/feishu/cards.js +560 -0
  24. package/dist/feishu/cards.js.map +1 -0
  25. package/dist/feishu/client.d.ts +132 -0
  26. package/dist/feishu/client.d.ts.map +1 -0
  27. package/dist/feishu/client.js +952 -0
  28. package/dist/feishu/client.js.map +1 -0
  29. package/dist/feishu/streamer.d.ts +30 -0
  30. package/dist/feishu/streamer.d.ts.map +1 -0
  31. package/dist/feishu/streamer.js +95 -0
  32. package/dist/feishu/streamer.js.map +1 -0
  33. package/dist/handlers/card-action.d.ts +12 -0
  34. package/dist/handlers/card-action.d.ts.map +1 -0
  35. package/dist/handlers/card-action.js +154 -0
  36. package/dist/handlers/card-action.js.map +1 -0
  37. package/dist/handlers/command.d.ts +76 -0
  38. package/dist/handlers/command.d.ts.map +1 -0
  39. package/dist/handlers/command.js +1773 -0
  40. package/dist/handlers/command.js.map +1 -0
  41. package/dist/handlers/discord.d.ts +78 -0
  42. package/dist/handlers/discord.d.ts.map +1 -0
  43. package/dist/handlers/discord.js +1832 -0
  44. package/dist/handlers/discord.js.map +1 -0
  45. package/dist/handlers/file-sender.d.ts +22 -0
  46. package/dist/handlers/file-sender.d.ts.map +1 -0
  47. package/dist/handlers/file-sender.js +183 -0
  48. package/dist/handlers/file-sender.js.map +1 -0
  49. package/dist/handlers/group.d.ts +21 -0
  50. package/dist/handlers/group.d.ts.map +1 -0
  51. package/dist/handlers/group.js +414 -0
  52. package/dist/handlers/group.js.map +1 -0
  53. package/dist/handlers/lifecycle.d.ts +17 -0
  54. package/dist/handlers/lifecycle.d.ts.map +1 -0
  55. package/dist/handlers/lifecycle.js +129 -0
  56. package/dist/handlers/lifecycle.js.map +1 -0
  57. package/dist/handlers/p2p.d.ts +44 -0
  58. package/dist/handlers/p2p.d.ts.map +1 -0
  59. package/dist/handlers/p2p.js +625 -0
  60. package/dist/handlers/p2p.js.map +1 -0
  61. package/dist/index.d.ts +33 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +1562 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/opencode/client.d.ts +176 -0
  66. package/dist/opencode/client.d.ts.map +1 -0
  67. package/dist/opencode/client.js +1126 -0
  68. package/dist/opencode/client.js.map +1 -0
  69. package/dist/opencode/delayed-handler.d.ts +33 -0
  70. package/dist/opencode/delayed-handler.d.ts.map +1 -0
  71. package/dist/opencode/delayed-handler.js +74 -0
  72. package/dist/opencode/delayed-handler.js.map +1 -0
  73. package/dist/opencode/output-buffer.d.ts +56 -0
  74. package/dist/opencode/output-buffer.d.ts.map +1 -0
  75. package/dist/opencode/output-buffer.js +202 -0
  76. package/dist/opencode/output-buffer.js.map +1 -0
  77. package/dist/opencode/question-handler.d.ts +61 -0
  78. package/dist/opencode/question-handler.d.ts.map +1 -0
  79. package/dist/opencode/question-handler.js +183 -0
  80. package/dist/opencode/question-handler.js.map +1 -0
  81. package/dist/opencode/question-parser.d.ts +9 -0
  82. package/dist/opencode/question-parser.d.ts.map +1 -0
  83. package/dist/opencode/question-parser.js +69 -0
  84. package/dist/opencode/question-parser.js.map +1 -0
  85. package/dist/opencode/session-queue.d.ts +16 -0
  86. package/dist/opencode/session-queue.d.ts.map +1 -0
  87. package/dist/opencode/session-queue.js +41 -0
  88. package/dist/opencode/session-queue.js.map +1 -0
  89. package/dist/permissions/handler.d.ts +36 -0
  90. package/dist/permissions/handler.d.ts.map +1 -0
  91. package/dist/permissions/handler.js +141 -0
  92. package/dist/permissions/handler.js.map +1 -0
  93. package/dist/platform/adapters/discord-adapter.d.ts +45 -0
  94. package/dist/platform/adapters/discord-adapter.d.ts.map +1 -0
  95. package/dist/platform/adapters/discord-adapter.js +497 -0
  96. package/dist/platform/adapters/discord-adapter.js.map +1 -0
  97. package/dist/platform/adapters/feishu-adapter.d.ts +29 -0
  98. package/dist/platform/adapters/feishu-adapter.d.ts.map +1 -0
  99. package/dist/platform/adapters/feishu-adapter.js +150 -0
  100. package/dist/platform/adapters/feishu-adapter.js.map +1 -0
  101. package/dist/platform/registry.d.ts +41 -0
  102. package/dist/platform/registry.d.ts.map +1 -0
  103. package/dist/platform/registry.js +87 -0
  104. package/dist/platform/registry.js.map +1 -0
  105. package/dist/platform/types.d.ts +92 -0
  106. package/dist/platform/types.d.ts.map +1 -0
  107. package/dist/platform/types.js +4 -0
  108. package/dist/platform/types.js.map +1 -0
  109. package/dist/reliability/audit-log.d.ts +93 -0
  110. package/dist/reliability/audit-log.d.ts.map +1 -0
  111. package/dist/reliability/audit-log.js +248 -0
  112. package/dist/reliability/audit-log.js.map +1 -0
  113. package/dist/reliability/config-guard.d.ts +42 -0
  114. package/dist/reliability/config-guard.d.ts.map +1 -0
  115. package/dist/reliability/config-guard.js +264 -0
  116. package/dist/reliability/config-guard.js.map +1 -0
  117. package/dist/reliability/conversation-heartbeat.d.ts +37 -0
  118. package/dist/reliability/conversation-heartbeat.d.ts.map +1 -0
  119. package/dist/reliability/conversation-heartbeat.js +179 -0
  120. package/dist/reliability/conversation-heartbeat.js.map +1 -0
  121. package/dist/reliability/cron-api-server.d.ts +13 -0
  122. package/dist/reliability/cron-api-server.d.ts.map +1 -0
  123. package/dist/reliability/cron-api-server.js +247 -0
  124. package/dist/reliability/cron-api-server.js.map +1 -0
  125. package/dist/reliability/cron-control.d.ts +34 -0
  126. package/dist/reliability/cron-control.d.ts.map +1 -0
  127. package/dist/reliability/cron-control.js +864 -0
  128. package/dist/reliability/cron-control.js.map +1 -0
  129. package/dist/reliability/cron-semantic.d.ts +9 -0
  130. package/dist/reliability/cron-semantic.d.ts.map +1 -0
  131. package/dist/reliability/cron-semantic.js +208 -0
  132. package/dist/reliability/cron-semantic.js.map +1 -0
  133. package/dist/reliability/environment-doctor.d.ts +56 -0
  134. package/dist/reliability/environment-doctor.d.ts.map +1 -0
  135. package/dist/reliability/environment-doctor.js +213 -0
  136. package/dist/reliability/environment-doctor.js.map +1 -0
  137. package/dist/reliability/job-registry.d.ts +26 -0
  138. package/dist/reliability/job-registry.d.ts.map +1 -0
  139. package/dist/reliability/job-registry.js +77 -0
  140. package/dist/reliability/job-registry.js.map +1 -0
  141. package/dist/reliability/opencode-probe.d.ts +37 -0
  142. package/dist/reliability/opencode-probe.d.ts.map +1 -0
  143. package/dist/reliability/opencode-probe.js +195 -0
  144. package/dist/reliability/opencode-probe.js.map +1 -0
  145. package/dist/reliability/opencode-restart.d.ts +42 -0
  146. package/dist/reliability/opencode-restart.d.ts.map +1 -0
  147. package/dist/reliability/opencode-restart.js +155 -0
  148. package/dist/reliability/opencode-restart.js.map +1 -0
  149. package/dist/reliability/proactive-heartbeat.d.ts +39 -0
  150. package/dist/reliability/proactive-heartbeat.d.ts.map +1 -0
  151. package/dist/reliability/proactive-heartbeat.js +147 -0
  152. package/dist/reliability/proactive-heartbeat.js.map +1 -0
  153. package/dist/reliability/process-check-job.d.ts +73 -0
  154. package/dist/reliability/process-check-job.d.ts.map +1 -0
  155. package/dist/reliability/process-check-job.js +254 -0
  156. package/dist/reliability/process-check-job.js.map +1 -0
  157. package/dist/reliability/process-guard.d.ts +53 -0
  158. package/dist/reliability/process-guard.d.ts.map +1 -0
  159. package/dist/reliability/process-guard.js +344 -0
  160. package/dist/reliability/process-guard.js.map +1 -0
  161. package/dist/reliability/recovery-reporter.d.ts +37 -0
  162. package/dist/reliability/recovery-reporter.d.ts.map +1 -0
  163. package/dist/reliability/recovery-reporter.js +161 -0
  164. package/dist/reliability/recovery-reporter.js.map +1 -0
  165. package/dist/reliability/rescue-executor.d.ts +52 -0
  166. package/dist/reliability/rescue-executor.d.ts.map +1 -0
  167. package/dist/reliability/rescue-executor.js +244 -0
  168. package/dist/reliability/rescue-executor.js.map +1 -0
  169. package/dist/reliability/rescue-policy.d.ts +39 -0
  170. package/dist/reliability/rescue-policy.d.ts.map +1 -0
  171. package/dist/reliability/rescue-policy.js +85 -0
  172. package/dist/reliability/rescue-policy.js.map +1 -0
  173. package/dist/reliability/runtime-cron-dispatcher.d.ts +30 -0
  174. package/dist/reliability/runtime-cron-dispatcher.d.ts.map +1 -0
  175. package/dist/reliability/runtime-cron-dispatcher.js +100 -0
  176. package/dist/reliability/runtime-cron-dispatcher.js.map +1 -0
  177. package/dist/reliability/runtime-cron-orphan.d.ts +18 -0
  178. package/dist/reliability/runtime-cron-orphan.d.ts.map +1 -0
  179. package/dist/reliability/runtime-cron-orphan.js +87 -0
  180. package/dist/reliability/runtime-cron-orphan.js.map +1 -0
  181. package/dist/reliability/runtime-cron-registry.d.ts +4 -0
  182. package/dist/reliability/runtime-cron-registry.d.ts.map +1 -0
  183. package/dist/reliability/runtime-cron-registry.js +8 -0
  184. package/dist/reliability/runtime-cron-registry.js.map +1 -0
  185. package/dist/reliability/runtime-cron.d.ts +75 -0
  186. package/dist/reliability/runtime-cron.d.ts.map +1 -0
  187. package/dist/reliability/runtime-cron.js +309 -0
  188. package/dist/reliability/runtime-cron.js.map +1 -0
  189. package/dist/reliability/scheduler.d.ts +38 -0
  190. package/dist/reliability/scheduler.d.ts.map +1 -0
  191. package/dist/reliability/scheduler.js +174 -0
  192. package/dist/reliability/scheduler.js.map +1 -0
  193. package/dist/reliability/types.d.ts +151 -0
  194. package/dist/reliability/types.d.ts.map +1 -0
  195. package/dist/reliability/types.js +178 -0
  196. package/dist/reliability/types.js.map +1 -0
  197. package/dist/router/action-handlers.d.ts +27 -0
  198. package/dist/router/action-handlers.d.ts.map +1 -0
  199. package/dist/router/action-handlers.js +226 -0
  200. package/dist/router/action-handlers.js.map +1 -0
  201. package/dist/router/opencode-event-hub.d.ts +159 -0
  202. package/dist/router/opencode-event-hub.d.ts.map +1 -0
  203. package/dist/router/opencode-event-hub.js +589 -0
  204. package/dist/router/opencode-event-hub.js.map +1 -0
  205. package/dist/router/root-router.d.ts +94 -0
  206. package/dist/router/root-router.d.ts.map +1 -0
  207. package/dist/router/root-router.js +214 -0
  208. package/dist/router/root-router.js.map +1 -0
  209. package/dist/store/chat-session.d.ts +150 -0
  210. package/dist/store/chat-session.d.ts.map +1 -0
  211. package/dist/store/chat-session.js +640 -0
  212. package/dist/store/chat-session.js.map +1 -0
  213. package/dist/store/session-directory.d.ts +12 -0
  214. package/dist/store/session-directory.d.ts.map +1 -0
  215. package/dist/store/session-directory.js +47 -0
  216. package/dist/store/session-directory.js.map +1 -0
  217. package/dist/store/session-group.d.ts +19 -0
  218. package/dist/store/session-group.d.ts.map +1 -0
  219. package/dist/store/session-group.js +92 -0
  220. package/dist/store/session-group.js.map +1 -0
  221. package/dist/store/user-session.d.ts +19 -0
  222. package/dist/store/user-session.d.ts.map +1 -0
  223. package/dist/store/user-session.js +112 -0
  224. package/dist/store/user-session.js.map +1 -0
  225. package/dist/utils/async-queue.d.ts +12 -0
  226. package/dist/utils/async-queue.d.ts.map +1 -0
  227. package/dist/utils/async-queue.js +51 -0
  228. package/dist/utils/async-queue.js.map +1 -0
  229. package/dist/utils/directory-policy.d.ts +50 -0
  230. package/dist/utils/directory-policy.d.ts.map +1 -0
  231. package/dist/utils/directory-policy.js +379 -0
  232. package/dist/utils/directory-policy.js.map +1 -0
  233. package/dist/utils/session-title.d.ts +2 -0
  234. package/dist/utils/session-title.d.ts.map +1 -0
  235. package/dist/utils/session-title.js +10 -0
  236. package/dist/utils/session-title.js.map +1 -0
  237. package/package.json +73 -0
package/README.md ADDED
@@ -0,0 +1,1195 @@
1
+ # Feishu / Discord × OpenCode 桥接服务 v2.9.0-beta
2
+
3
+ [![v2.9.0-beta](https://img.shields.io/badge/v2.9.0--beta-3178C6)]()
4
+ [![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E%3D18-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
+ [![License: GPLv3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
7
+
8
+
9
+ 这是一个跨平台桥接层,不只服务飞书,也服务 Discord。`v2.9.0-beta` 将核心从“单文件堆逻辑”重构为“平台适配层 + 根路由器 + OpenCode 事件中枢 + 领域处理器”,重点解决跨平台扩展、权限闭环稳定性、目录实例一致性和线上可维护性。
10
+
11
+ 随着运行时 Cron(API + `/cron` + `///cron` + 自然语言语义解析)与本地可靠性治理落地,本项目和 OpenClaw 在“自动化调度 + 运维可用性”上的能力差距进一步缩小,同时保留本项目在多平台路由与权限闭环上的工程优势。
12
+
13
+ <a id="先看痛点"></a>
14
+ ## 🎯 先看痛点
15
+
16
+ - 权限和提问链路一旦断路,任务会卡住:`permission.asked` / `question.asked` 必须形成严格闭环。
17
+ - 多平台并行时最怕串线:会话绑定、权限队列、输出缓冲如果不按平台隔离,异常很难排查。
18
+ - 切目录后的实例一致性是高频坑:日志显示“允许”,实际 OpenCode 目录实例未命中,任务仍会挂起。
19
+ - 卡片与文本交互模型差异大:飞书与 Discord 不能硬复制同一交互范式,必须走平台原生能力。
20
+ - 运维闭环要求越来越高:仅靠“能跑起来”不够,必须可验证、可回滚、可灰度。
21
+
22
+ 这个项目解决的不是“能不能回复消息”,而是“跨平台 AI 任务能否长期稳定闭环”。
23
+
24
+ <a id="与-openclaw-和同类桥接对比"></a>
25
+ ## 🥊 与 OpenClaw 和同类桥接对比
26
+
27
+ > 结论:如果目标只是“在聊天里接一个 AI”,很多桥接都能满足;如果你要的是“跨平台 + 权限/提问/会话/目录/调度/回滚/运维”一体化工程链路,这个项目更适合生产化长期使用。
28
+
29
+ | 维度 | OpenClaw / 同类桥接常见形态 | 本项目(v2.9.0-beta) |
30
+ |---|---|---|
31
+ | 架构形态 | 消息通路优先,功能按需叠加 | 平台适配 + RootRouter + EventHub + Domain 分层 |
32
+ | 平台扩展 | 单平台能力迁移成本高 | Feishu / Discord 平台差异显式建模,能力独立演进 |
33
+ | 权限闭环 | 常见“提示后等待人工” | 白名单自动允许 + 失败降级入队 + 文本/卡片双通道确认 |
34
+ | 目录一致性 | 目录切换后状态易错位 | 权限响应目录感知,支持候选目录回退,降低假死概率 |
35
+ | 流式输出 | 多为单轨文本 | Timeline 统一聚合,支持 thinking/tool 可见性平台级开关 |
36
+ | 会话绑定 | 常见仅 chat->session 单向映射 | `platform:conversationId` 命名空间 + alias 回溯 |
37
+ | 运维能力 | 依赖脚本拼装 | 部署/升级/检查/后台/systemd 一体化入口 |
38
+
39
+ 注:左列为常见实现归纳,不代表任一具体项目的完整能力清单;请以各项目当期版本文档为准。
40
+
41
+ 新时代了,让 AI 代理自动部署吧:请在 OpenCode 执行如下指令:
42
+ ```bash
43
+ 请阅读 https://github.com/HNGM-HP/opencode-bridge/blob/main/AI_Deployment_Guide.md 文档并安装程序
44
+ ```
45
+
46
+ 也可以作为 npm CLI 安装(更适合本地常驻运行场景):
47
+
48
+ ```bash
49
+ npm install -g opencode-bridge
50
+ opencode-bridge
51
+ ```
52
+
53
+ 说明:
54
+
55
+ - npm 包主要提供 CLI 分发与版本管理便利,不替代 OpenCode 本地服务与飞书/Discord 配置。
56
+ - 运行前仍需准备 `.env`、本地 `opencode serve`,以及对应平台的机器人凭据。
57
+ - CLI 默认优先读取当前工作目录下的 `.env`;若当前目录没有 `.env`,会自动回退读取 `~/.config/opencode-bridge/.env`。
58
+ - 你也可以显式指定配置目录:`opencode-bridge --config-dir /path/to/config`。
59
+ - 若你偏好源码部署,继续使用仓库里的 `scripts/deploy.*` / `scripts/start.*` 也完全没问题。
60
+
61
+ 推荐的 npm CLI 配置方式:
62
+
63
+ ```bash
64
+ mkdir -p ~/.config/opencode-bridge
65
+
66
+ # 若你是通过 npm 全局安装:
67
+ cp "$(npm root -g)/opencode-bridge/.env.example" ~/.config/opencode-bridge/.env
68
+
69
+ # 若你是源码仓库内运行:
70
+ # cp .env.example ~/.config/opencode-bridge/.env
71
+
72
+ # 直接使用默认配置目录启动
73
+ opencode-bridge
74
+
75
+ # 或者在当前目录放独立 .env
76
+ mkdir -p ~/opencode-bridge-prod
77
+ cp .env.example ~/opencode-bridge-prod/.env
78
+ cd ~/opencode-bridge-prod
79
+ opencode-bridge
80
+
81
+ # 也可以显式指定配置目录
82
+ opencode-bridge --config-dir ~/.config/opencode-bridge
83
+ ```
84
+
85
+ ## 📋 目录
86
+
87
+ - [先看痛点](#先看痛点)
88
+ - [与 OpenClaw 和同类桥接对比](#与-openclaw-和同类桥接对比)
89
+ - [为什么用它](#为什么用它)
90
+ - [能力总览](#能力总览)
91
+ - [效果演示](#效果演示)
92
+ - [架构概览](#架构概览)
93
+ - [快速开始](#快速开始)
94
+ - [部署与运维](#部署与运维)
95
+ - [环境变量](#环境变量)
96
+ - [可靠性能力(心跳 + Cron + 宕机救援)](#reliability-guide)
97
+ - [飞书后台配置](#飞书后台配置)
98
+ - [命令速查](#命令速查)
99
+ - [关键实现细节](#关键实现细节)
100
+ - [故障排查](#故障排查)
101
+
102
+ <a id="为什么用它"></a>
103
+ ## 💡为什么用它
104
+
105
+ - 对使用者友好:权限确认、question 作答、会话操作都在飞书里完成,不强依赖本地终端。
106
+ - 对协作友好:支持绑定已有会话与迁移绑定,跨设备、跨群接力时上下文不断裂。
107
+ - 对稳定性友好:会话映射持久化 + 双端撤回 + 同规则清理,避免“表面正常、状态错位”。
108
+ - 对运维友好:内置部署、升级、状态检查与后台管理流程,适合持续托管运行。
109
+ - 对未来版本友好:已兼容 OpenCode Server Basic Auth,服务端启用密码后仍可直接接入。
110
+
111
+ <a id="能力总览"></a>
112
+ ## 📸 能力总览
113
+
114
+ | 能力 | 你能得到什么 | 相关命令/配置 |
115
+ |---|---|---|
116
+ | 群聊/私聊统一路由 | 同一套入口支持私聊和群聊,按映射路由到正确会话 | 群聊 @ 机器人;私聊直接发消息 |
117
+ | 私聊建群会话选择 | 建群时可选“新建会话/绑定已有会话”,提交时按选择生效 | `/create_chat`、`/建群` |
118
+ | 手动会话绑定 | 不中断旧上下文,直接把指定 session 接入当前群 | `/session <sessionId>`、`ENABLE_MANUAL_SESSION_BIND` |
119
+ | 迁移绑定与删除保护 | 绑定已有会话时自动迁移旧群映射,并保护会话不被误删 | 自动生效(手动绑定场景) |
120
+ | 生命周期清理兜底 | 启动清理与手动清理共用同一规则,降低误清理概率 | `/clear free session` |
121
+ | 权限卡片闭环 | OpenCode 权限请求在飞书内完成确认并回传结果 | `permission.asked` |
122
+ | question 卡片闭环 | OpenCode question 在飞书内回答/跳过并继续任务 | `question.asked` |
123
+ | 流式多卡防溢出 | 超过组件预算自动分页拆卡,旧页持续更新 | 流式卡片分页(预算 180) |
124
+ | 双端撤回一致性 | 撤回时同时回滚飞书消息与 OpenCode 会话状态 | `/undo` |
125
+ | 模型/角色/强度可视化控制 | 按会话切换模型、角色与推理强度,支持面板查看与命令操作 | `/panel`、`/model`、`/agent`、`/effort` |
126
+ | 上下文压缩 | 在飞书直接触发会话 summarize,释放上下文窗口 | `/compact` |
127
+ | Shell 命令透传 | 白名单 `!` 命令通过 OpenCode shell 执行并回显输出 | `!ls`、`!pwd`、`!git status` |
128
+ | 服务端鉴权兼容 | 支持 OpenCode Server Basic Auth,不怕后续默认强制密码 | `OPENCODE_SERVER_USERNAME`、`OPENCODE_SERVER_PASSWORD` |
129
+ | 文件发送到飞书 | AI 可将电脑上的文件/截图直接发送到当前飞书群聊 | `/send`、`发送文件` |
130
+ | 工作目录/项目管理 | 创建会话时指定工作目录,支持项目别名、群默认项目、9 阶段安全校验 | `/project list`、`/session new <别名>`、`ALLOWED_DIRECTORIES` |
131
+ | OpenCode 本地可靠性治理 | 运行时 Cron(API/命令/自然语言)+ 本地宕机自动救援(含配置备份/两级回退)+ 可选主动心跳 | `HEARTBEAT.md`、`RELIABILITY_*`、`logs/reliability-audit.jsonl` |
132
+ | 部署运维闭环 | 提供部署/升级/检查/后台/systemd 的一体化入口 | `scripts/deploy.*`、`scripts/start.*` |
133
+
134
+ <a id="效果演示"></a>
135
+ ## 🖼️ 效果演示
136
+
137
+ 折叠展示图片,下面按场景整理:
138
+
139
+ <details>
140
+ <summary>Step 1:私聊独立会话(点击展开)</summary>
141
+
142
+ <p>
143
+ <img src="assets/demo/1-1私聊独立会话.png" width="720" />
144
+ <img src="assets/demo/1-2私聊独立会话.png" width="720" />
145
+ <img src="assets/demo/1-3私聊独立会话.png" width="720" />
146
+ <img src="assets/demo/1-4私聊独立会话.png" width="720" />
147
+ </p>
148
+
149
+ </details>
150
+
151
+ <details>
152
+ <summary>Step 2:多群聊独立会话(点击展开)</summary>
153
+
154
+ <p>
155
+ <img src="assets/demo/2-1多群聊独立会话.png" width="720" />
156
+ <img src="assets/demo/2-2多群聊独立会话.png.png" width="720" />
157
+ <img src="assets/demo/2-3多群聊独立会话.png.png" width="720" />
158
+ </p>
159
+
160
+ </details>
161
+
162
+ <details>
163
+ <summary>Step 3:图片附件解析(点击展开)</summary>
164
+
165
+ <p>
166
+ <img src="assets/demo/3-1图片附件解析.png" width="720" />
167
+ <img src="assets/demo/3-2图片附件解析.png.png" width="720" />
168
+ <img src="assets/demo/3-3图片附件解析.png.png" width="720" />
169
+ </p>
170
+
171
+ </details>
172
+
173
+ <details>
174
+ <summary>Step 4:交互工具测试(点击展开)</summary>
175
+
176
+ <p>
177
+ <img src="assets/demo/4-1交互工具测试.png" width="720" />
178
+ <img src="assets/demo/4-2交互工具测试.png.png" width="720" />
179
+ </p>
180
+
181
+ </details>
182
+
183
+ <details>
184
+ <summary>Step 5:底层权限测试(点击展开)</summary>
185
+
186
+ <p>
187
+ <img src="assets/demo/5-1底层权限测试.png" width="720" />
188
+ <img src="assets/demo/5-2底层权限测试.png.png" width="720" />
189
+ <img src="assets/demo/5-3底层权限测试.png.png" width="720" />
190
+ <img src="assets/demo/5-4底层权限测试.png.png" width="720" />
191
+ </p>
192
+
193
+ </details>
194
+
195
+ <details>
196
+ <summary>Step 6:会话清理(点击展开)</summary>
197
+
198
+ <p>
199
+ <img src="assets/demo/6-1会话清理.png" width="720" />
200
+ <img src="assets/demo/6-2会话清理.png.png" width="720" />
201
+ <img src="assets/demo/6-3会话清理.png.png" width="720" />
202
+ </p>
203
+
204
+ </details>
205
+
206
+ <a id="架构概览"></a>
207
+ ## 📌 架构概览
208
+
209
+ ```mermaid
210
+ flowchart TB
211
+ subgraph Platforms[平台接入层]
212
+ FE[Feishu Adapter]
213
+ DC[Discord Adapter]
214
+ end
215
+
216
+ subgraph Ingress[入口与路由层]
217
+ RR[RootRouter\nFeishu 消息/卡片动作]
218
+ DH[DiscordHandler\n频道消息/命令]
219
+ end
220
+
221
+ subgraph Domain[领域服务层]
222
+ PH[PermissionHandler]
223
+ QH[QuestionHandler]
224
+ OB[OutputBuffer]
225
+ CS[ChatSessionStore]
226
+ LC[LifecycleHandler]
227
+ end
228
+
229
+ subgraph OpenCode[OpenCode 集成层]
230
+ OC[OpencodeClientWrapper]
231
+ EH[OpenCodeEventHub]
232
+ end
233
+
234
+ FE --> RR
235
+ DC --> DH
236
+ RR --> PH
237
+ RR --> QH
238
+ RR --> OB
239
+ DH --> OC
240
+ PH --> OC
241
+ QH --> OC
242
+ EH --> OB
243
+ EH --> PH
244
+ EH --> QH
245
+ Domain <--> CS
246
+ OC <--> EH
247
+ OC <--> OpenCodeServer[(OpenCode Server)]
248
+ LC --> CS
249
+ ```
250
+ - [项目架构](assets/docs/architecture.md)
251
+ - [OpenCode-sdk-api](assets/docs/sdk-api.md)
252
+
253
+ ### 分层说明(v2.9.0-beta)
254
+
255
+ 1. **平台接入层(Adapter)**
256
+ - Feishu: 长连接事件 + 卡片交互,能力完整(权限/问题/流式卡片)。
257
+ - Discord: 网关消息接入 + 文本回复 + 编辑/删除 + 会话命令,默认关闭按需启用。
258
+
259
+ 2. **入口与路由层(Ingress)**
260
+ - Feishu 走 `RootRouter`,维持既有权限卡片、问题卡片、双轨日志能力。
261
+ - Discord 走 `DiscordHandler`,优先保证稳定问答闭环,不强行复制不适合 Discord 的卡片交互。
262
+
263
+ 3. **领域服务层(Domain)**
264
+ - `ChatSessionStore`: 统一会话命名空间(`platform:conversationId`),解决多平台同 ID 冲突。
265
+ - `OutputBuffer`: 流式输出合并与节流,避免高频更新触发平台限流。
266
+ - `PermissionHandler` / `QuestionHandler`: 管理 OpenCode 交互状态机与回路由。
267
+
268
+ 4. **OpenCode 集成层**
269
+ - `OpencodeClientWrapper`: 会话创建、消息发送、权限/问题回复、目录实例管理。
270
+ - `OpenCodeEventHub`: 单监听入口,统一分发事件到缓冲区与交互处理器。
271
+
272
+ ### 平台能力矩阵(当前实现)
273
+
274
+ | 能力 | Feishu | Discord | 设计取舍 |
275
+ |---|---|---|---|
276
+ | 消息接入(群/私聊) | ✅ | ✅ | 两端都支持 |
277
+ | 会话自动创建/绑定 | ✅ | ✅ | 统一走 `ChatSessionStore` |
278
+ | 群聊仅 @ 才响应 | ✅(`GROUP_REQUIRE_MENTION`) | ✅(同开关) | 降低噪声,兼容默认行为 |
279
+ | 流式更新 | ✅ | ✅ | 飞书卡片消息回复,Discord 文本回复 |
280
+ | 权限卡片闭环 | ✅ | ✅(Button/Select + 文本兜底) | Discord 原生组件交互,文本回复支持 allow/reject/always |
281
+ | question 卡片闭环 | ✅ | ✅(Select + 文本兜底) | 保持平台特性,不做硬复制 |
282
+ | 消息编辑/删除 | ✅ | ✅ | Discord Sender 已支持 |
283
+ ### Discord 对齐策略(扬长避短)
284
+
285
+
286
+ - **优先落地**:消息稳定收发、会话绑定、@ 触发治理、可观测日志与可回滚配置。
287
+ - **利用优势**:Discord 文本链路低摩擦,先交付高可用问答与命令链路(`///session`、`///new`、`///clear`,并兼容旧前缀)。
288
+ - **避免短板硬上**:对 Discord 不天然友好的“飞书式卡片工作流”不做粗暴复制,后续按组件交互逐步演进。
289
+
290
+ <a id="快速开始"></a>
291
+ ## 🚀 快速开始
292
+
293
+ ### 1) 先执行这一条命令(首选)
294
+
295
+ Linux/macOS:
296
+
297
+ ```bash
298
+ ./scripts/deploy.sh guide
299
+ ```
300
+
301
+ Windows PowerShell:
302
+
303
+ ```powershell
304
+ .\scripts\deploy.ps1 guide
305
+ ```
306
+
307
+ 这条命令会自动完成:
308
+ - 检测 Node.js / npm(缺失时给安装引导)
309
+ - 检测 OpenCode 安装与端口状态
310
+ - 可一键安装 OpenCode(`npm i -g opencode-ai`)
311
+ - 安装项目依赖并编译桥接服务
312
+ - 若 `.env` 不存在,会自动由 `.env.example` 复制生成(不会覆盖已有 `.env`)
313
+ - 可在交互阶段直接输入 `FEISHU_APP_ID` / `FEISHU_APP_SECRET` 并写入 `.env`(支持回撤/跳过)
314
+
315
+ 提醒:
316
+ - 不添加`guide`后缀执行命令为菜单。
317
+ - 这一条命令可以完成“部署与环境准备”。
318
+ - 但飞书密钥需要你自己填,脚本不会替你写入真实凭据;未填写时服务无法正常接收飞书消息。
319
+
320
+ ### 2) 填写飞书配置(必须,若上一步已输入可跳过)
321
+
322
+ ```bash
323
+ cp .env.example .env
324
+ ```
325
+
326
+ 至少填写:
327
+ - `FEISHU_APP_ID`
328
+ - `FEISHU_APP_SECRET`
329
+
330
+ ### 3) 启动 OpenCode(保留 CLI 界面)
331
+
332
+ 推荐在菜单里执行“启动 OpenCode CLI(自动写入 server 配置)”,或直接运行:
333
+
334
+ ```bash
335
+ opencode
336
+ ```
337
+
338
+ ### 4) 启动桥接服务
339
+
340
+ Linux/macOS:
341
+
342
+ ```bash
343
+ ./scripts/start.sh
344
+ ```
345
+
346
+ Windows PowerShell:
347
+
348
+ ```powershell
349
+ .\scripts\start.ps1
350
+ ```
351
+
352
+ 开发调试可用:
353
+
354
+ ```bash
355
+ npm run dev
356
+ ```
357
+
358
+ <a id="部署与运维"></a>
359
+ ## 💻 部署与运维
360
+
361
+ ### 零门槛入口(推荐)
362
+
363
+ | 平台 | 管理菜单 | 一键部署 | 一键更新升级 | 启动后台 | 停止后台 |
364
+ |---|---|---|---|---|---|
365
+ | Linux/macOS | `./scripts/deploy.sh menu` | `./scripts/deploy.sh deploy` | `./scripts/deploy.sh upgrade` | `./scripts/start.sh` | `./scripts/stop.sh` |
366
+ | Windows PowerShell | `.\\scripts\\deploy.ps1 menu` | `.\\scripts\\deploy.ps1 deploy` | `.\\scripts\\deploy.ps1 upgrade` | `.\\scripts\\start.ps1` | `.\\scripts\\stop.ps1` |
367
+
368
+ 说明:
369
+ - `deploy.sh`(Linux/macOS)和 `deploy.ps1`(Windows)会先自动检测 Node.js 与 npm。
370
+ - **Windows**:若未检测到 Node.js,会询问是否自动安装(优先使用 winget,其次 choco),安装后自动重试。
371
+ - **Linux/macOS**:若未检测到,会询问是否显示安装引导,再让用户确认是否重试检测。
372
+ - 菜单内已包含 OpenCode 的安装/检查/启动与首次引导,部署时会额外给出 OpenCode 安装与端口检查强提示(不阻断部署)。
373
+
374
+ ### 已安装 Node 后可用命令
375
+
376
+ | 目标 | 命令 | 说明 |
377
+ |---|---|---|
378
+ | 一键部署 | `node scripts/deploy.mjs deploy` | 安装依赖并编译 |
379
+ | 一键更新升级 | `node scripts/deploy.mjs upgrade` | 先拆卸清理,再拉取并重新部署(保留升级脚本) |
380
+ | 安装/升级 OpenCode | `node scripts/deploy.mjs opencode-install` | 执行 `npm i -g opencode-ai` |
381
+ | 检查 OpenCode 环境 | `node scripts/deploy.mjs opencode-check` | 检查 opencode 命令与端口监听 |
382
+ | 启动 OpenCode CLI | `node scripts/deploy.mjs opencode-start` | 自动写入 `opencode.json` 后前台执行 `opencode` |
383
+ | 首次引导 | `node scripts/deploy.mjs guide` | 安装/部署/引导启动的一体化流程 |
384
+ | 管理菜单 | `node scripts/deploy.mjs menu` | 交互式菜单(默认入口) |
385
+ | 启动后台 | `node scripts/start.mjs` | 后台启动(自动检测/补构建) |
386
+ | 停止后台 | `node scripts/stop.mjs` | 按 PID 停止后台进程 |
387
+
388
+ ### Linux 常驻(systemd)
389
+
390
+ 管理菜单内提供以下操作:
391
+
392
+ - 安装并启动 systemd 服务
393
+ - 停止并禁用 systemd 服务
394
+ - 卸载 systemd 服务
395
+ - 查看运行状态
396
+
397
+ 也可以直接命令行调用:
398
+
399
+ ```bash
400
+ sudo node scripts/deploy.mjs service-install
401
+ sudo node scripts/deploy.mjs service-disable
402
+ sudo node scripts/deploy.mjs service-uninstall
403
+ node scripts/deploy.mjs status
404
+ ```
405
+
406
+ 日志默认在 `logs/service.log` 和 `logs/service.err`。
407
+
408
+ <a id="环境变量"></a>
409
+ ## ⚙️ 环境变量
410
+
411
+ 以 `src/config.ts` 与 `src/index.ts` 实际读取为准:
412
+
413
+ | 变量 | 必填 | 默认值 | 说明 |
414
+ |---|---|---|---|
415
+ | `FEISHU_APP_ID` | 是 | - | 飞书应用 App ID |
416
+ | `FEISHU_APP_SECRET` | 是 | - | 飞书应用 App Secret |
417
+ | `ROUTER_MODE` | 否 | `legacy` | 路由模式:`legacy`/`dual`/`router` |
418
+ | `ENABLED_PLATFORMS` | 否 | - | 平台白名单,逗号分隔(如 `feishu,discord`) |
419
+ | `GROUP_REQUIRE_MENTION` | 否 | `false` | 为 `true` 时,群聊仅在明确 @ 机器人时响应 |
420
+ | `OPENCODE_HOST` | 否 | `localhost` | OpenCode 地址 |
421
+ | `OPENCODE_PORT` | 否 | `4096` | OpenCode 端口 |
422
+ | `DISCORD_ENABLED` | 否 | `false` | 是否启用 Discord 适配器 |
423
+ | `DISCORD_TOKEN` | 否 | - | Discord Bot Token(优先) |
424
+ | `DISCORD_BOT_TOKEN` | 否 | - | Discord Bot Token(兼容别名) |
425
+ | `DISCORD_CLIENT_ID` | 否 | - | Discord 应用 Client ID |
426
+ | `OPENCODE_SERVER_USERNAME` | 否 | `opencode` | OpenCode Server Basic Auth 用户名 |
427
+ | `OPENCODE_SERVER_PASSWORD` | 否 | - | OpenCode Server Basic Auth 密码 |
428
+ | `ALLOWED_USERS` | 否 | - | 飞书 open_id 白名单,逗号分隔;为空时不启用白名单 |
429
+ | `ENABLE_MANUAL_SESSION_BIND` | 否 | `true` | 是否允许“绑定已有 OpenCode 会话”;关闭后仅允许新建会话 |
430
+ | `TOOL_WHITELIST` | 否 | `Read,Glob,Grep,Task` | 自动放行权限标识列表 |
431
+ | `PERMISSION_REQUEST_TIMEOUT_MS` | 否 | `0` | 权限请求在桥接侧的保留时长(毫秒);`<=0` 表示不超时,持续等待回复 |
432
+ | `OUTPUT_UPDATE_INTERVAL` | 否 | `3000` | 输出刷新间隔(ms) |
433
+ | `ATTACHMENT_MAX_SIZE` | 否 | `52428800` | 附件大小上限(字节) |
434
+ | `ALLOWED_DIRECTORIES` | 否 | - | 允许的工作目录根列表,逗号分隔绝对路径;未配置时禁止用户自定义路径,同时 `/send` 文件发送会直接拒绝 |
435
+ | `DEFAULT_WORK_DIRECTORY` | 否 | - | 全局默认工作目录(最低优先级兜底),不配置则跟随 OpenCode 服务端 |
436
+ | `PROJECT_ALIASES` | 否 | `{}` | 项目别名 JSON 映射(如 `{"fe":"/home/user/fe"}`),支持短名创建会话 |
437
+ | `GIT_ROOT_NORMALIZATION` | 否 | `true` | 是否自动将目录归一到 Git 仓库根目录 |
438
+ | `SHOW_THINKING_CHAIN` | 否 | `true` | 全局默认:是否显示 AI 思维链(thinking 内容) |
439
+ | `SHOW_TOOL_CHAIN` | 否 | `true` | 全局默认:是否显示工具调用链 |
440
+ | `FEISHU_SHOW_THINKING_CHAIN` | 否 | - | 飞书专用:覆盖全局 `SHOW_THINKING_CHAIN`,未设置时继承全局值 |
441
+ | `FEISHU_SHOW_TOOL_CHAIN` | 否 | - | 飞书专用:覆盖全局 `SHOW_TOOL_CHAIN`,未设置时继承全局值 |
442
+ | `DISCORD_SHOW_THINKING_CHAIN` | 否 | - | Discord 专用:覆盖全局 `SHOW_THINKING_CHAIN`,未设置时继承全局值 |
443
+ | `DISCORD_SHOW_TOOL_CHAIN` | 否 | - | Discord 专用:覆盖全局 `SHOW_TOOL_CHAIN`,未设置时继承全局值 |
444
+ | `RELIABILITY_CRON_ENABLED` | 否 | `true` | 是否启用可靠性 Cron 调度器 |
445
+ | `RELIABILITY_CRON_API_ENABLED` | 否 | `false` | 是否启用运行时 Cron HTTP API |
446
+ | `RELIABILITY_CRON_API_HOST` | 否 | `127.0.0.1` | Cron API 监听地址 |
447
+ | `RELIABILITY_CRON_API_PORT` | 否 | `4097` | Cron API 监听端口 |
448
+ | `RELIABILITY_CRON_API_TOKEN` | 否 | - | Cron API Bearer Token(启用后请求需带 Authorization 头) |
449
+ | `RELIABILITY_CRON_JOBS_FILE` | 否 | `~/cron/jobs.json` | 运行时 Cron 任务持久化文件 |
450
+ | `RELIABILITY_CRON_ORPHAN_AUTO_CLEANUP` | 否 | `false` | 是否自动清理僵尸 Cron(启动扫描 / 群解散或频道删除联动 / stale cleanup) |
451
+ | `RELIABILITY_CRON_FORWARD_TO_PRIVATE` | 否 | `false` | 原聊天窗口失效时,是否允许转发到私聊或备用窗口 |
452
+ | `RELIABILITY_CRON_FALLBACK_FEISHU_CHAT_ID` | 否 | - | Feishu 备用接收 chat_id |
453
+ | `RELIABILITY_CRON_FALLBACK_DISCORD_CONVERSATION_ID` | 否 | - | Discord 备用接收频道/私聊 conversationId |
454
+ | `RELIABILITY_PROACTIVE_HEARTBEAT_ENABLED` | 否 | `false` | 是否启用 Bridge 主动心跳定时器 |
455
+ | `RELIABILITY_INBOUND_HEARTBEAT_ENABLED` | 否 | `false` | 是否启用“入站消息触发心跳”(兼容模式) |
456
+ | `RELIABILITY_HEARTBEAT_INTERVAL_MS` | 否 | `1800000` | Bridge 主动心跳轮询间隔(毫秒) |
457
+ | `RELIABILITY_HEARTBEAT_AGENT` | 否 | - | 主动心跳发送到 OpenCode 时使用的 agent |
458
+ | `RELIABILITY_HEARTBEAT_PROMPT` | 否 | 内置默认提示 | 主动心跳提示词(建议包含 HEARTBEAT_OK 约定) |
459
+ | `RELIABILITY_HEARTBEAT_ALERT_CHATS` | 否 | - | 心跳告警推送目标飞书 chat_id(逗号分隔) |
460
+ | `RELIABILITY_FAILURE_THRESHOLD` | 否 | `3` | 无限重连场景下,触发自动救援所需的连续失败次数 |
461
+ | `RELIABILITY_WINDOW_MS` | 否 | `90000` | 无限重连场景下,失败统计窗口(毫秒) |
462
+ | `RELIABILITY_COOLDOWN_MS` | 否 | `300000` | 两次自动救援之间的冷却时间(毫秒) |
463
+ | `RELIABILITY_REPAIR_BUDGET` | 否 | `3` | 自动救援预算(耗尽后转人工介入) |
464
+ | `RELIABILITY_MODE` | 否 | `observe` | 可靠性模式预留字段(当前版本以阈值/预算策略为准) |
465
+ | `RELIABILITY_LOOPBACK_ONLY` | 否 | `true` | 是否只允许对 `localhost/127.0.0.1/::1` 执行自动救援 |
466
+ | `OPENCODE_CONFIG_FILE` | 否 | `./opencode.json` | 宕机救援时用于备份与回退的 OpenCode 配置文件路径 |
467
+
468
+
469
+ 注意:`TOOL_WHITELIST` 做字符串匹配,权限事件可能使用 `permission` 字段值(例如 `external_directory`),请按实际标识配置。
470
+
471
+ 如果 OpenCode 端开启了 `OPENCODE_SERVER_PASSWORD`,桥接端也必须配置同一组 `OPENCODE_SERVER_USERNAME`/`OPENCODE_SERVER_PASSWORD`,否则会出现 401/403 认证失败。
472
+
473
+ 模型默认策略:仅当 `DEFAULT_PROVIDER` 与 `DEFAULT_MODEL` 同时配置时,桥接才会显式指定模型;否则由 OpenCode 自身默认模型决定。
474
+
475
+ `ALLOWED_USERS` 说明:
476
+
477
+ - 未配置或留空:不启用白名单;生命周期清理仅在群成员数为 `0` 时才会自动解散群聊。
478
+ - 已配置:启用白名单保护;当群成员不足且群内/群主都不在白名单时,才会自动解散。
479
+
480
+ 手动绑定会话说明(`ENABLE_MANUAL_SESSION_BIND=true` 时):
481
+
482
+ - 通过 `/session <sessionId>` 或建群下拉卡片绑定已有会话后,会默认标记为“删除保护”。
483
+ - 自动清理与 `/clear free session` 仍可解散群聊并移除绑定,但会跳过 OpenCode `deleteSession`。
484
+
485
+ `ENABLE_MANUAL_SESSION_BIND` 取值语义:
486
+
487
+ - `true`:允许 `/session <sessionId>`,且建群卡片可选择“绑定已有会话”。
488
+ - `false`:禁用手动绑定能力;建群卡片仅保留“新建会话”。
489
+
490
+ `ALLOWED_DIRECTORIES` 说明:
491
+
492
+ - 未配置或留空:禁止用户通过 `/session new <path>` 自定义路径;仅允许使用默认目录、项目别名或从已知项目列表选择。
493
+ - 已配置:用户输入的路径经规范化与 realpath 解析后,必须落在允许根目录之下(含子目录),否则拒绝。
494
+ - 多个根目录用逗号分隔,如 `ALLOWED_DIRECTORIES=/home/user/projects,/opt/repos`。
495
+ - Windows 系统支持 Windows 格式路径,可使用正斜杠 `/` 或反斜杠 `\` 作为路径分隔符,如 `ALLOWED_DIRECTORIES=C:\Users\YourName\Documents,D:/Projects`。
496
+
497
+ `PROJECT_ALIASES` 说明:
498
+
499
+ - JSON 格式映射短名到绝对路径,如 `{"frontend":"/home/user/frontend"}`。
500
+ - 用户可通过 `/session new frontend` 使用别名创建会话,无需记忆完整路径。
501
+ - 别名路径同样受 `ALLOWED_DIRECTORIES` 约束。
502
+
503
+ <a id="reliability-guide"></a>
504
+ ## 🛡️ 可靠性能力(心跳 + Cron + 宕机救援)
505
+
506
+ ### 1) 默认行为
507
+
508
+ - 启动桥接服务后会自动初始化可靠性生命周期(心跳引擎 + Cron 调度 + 救援编排)。
509
+ - 默认情况下主动心跳关闭(`RELIABILITY_PROACTIVE_HEARTBEAT_ENABLED=false`);若开启后由 Bridge 定时器触发,不依赖飞书入站消息。
510
+ - 内置 Cron 任务默认启用:
511
+ - `watchdog-probe`: 每 30 秒
512
+ - `process-consistency-check`: 每 60 秒
513
+ - `stale-cleanup`: 每 5 分钟(当前版本为调度占位)
514
+ - `budget-reset`: 每天 0 点
515
+
516
+ ### 2) Cron 运行时动态管理(无需改代码)
517
+
518
+ 当前提供三种入口,底层共用同一 `RuntimeCronManager` 与同一持久化文件:
519
+
520
+ - HTTP API:`/cron/list|add|update|remove`
521
+ - Feishu:`/cron ...`
522
+ - Discord:`///cron ...`
523
+ - 默认行为:Cron 任务会绑定“创建它的聊天窗口 + 当时绑定的 OpenCode 会话”,到点后优先在原会话执行,并把结果回推到原聊天窗口。
524
+
525
+ 同时支持 `/cron` 与 `///cron` 的自然语言语义解析,例如:
526
+
527
+ - `/cron 添加个定时任务,每天早上8点向我发送一份AI简报`
528
+ - `///cron 生产AI简报,工作日记得发我`
529
+ - `/cron 暂停任务 <jobId>`
530
+
531
+ 通过本地 HTTP API 动态增删改查:
532
+
533
+ - `GET /cron/list`:列出任务
534
+ - `POST /cron/add`:新增任务
535
+ - `POST /cron/update`:更新任务
536
+ - `POST /cron/remove`:删除任务
537
+
538
+ 任务持久化到 `RELIABILITY_CRON_JOBS_FILE`(默认 `~/cron/jobs.json`),服务重启后自动恢复。若当前聊天没有绑定 OpenCode 会话,则 `/cron add ...` 会拒绝创建,避免后续退化成新开匿名会话执行。
539
+
540
+ `/cron list` 现在会额外展示目标窗口、孤儿状态和候选回退目标,例如:
541
+
542
+ ```text
543
+ 🕒 运行时 Cron 任务列表
544
+ (状态基于本地绑定表;fallback 为候选目标)
545
+ - [启用] 7c0d... | 国际新闻简报 | 0 0 18 * * *
546
+ text: 给我推送今天的国际新闻
547
+ target: feishu:oc_xxx(本地绑定有效) | session: ses_xxx
548
+ orphan: 否
549
+ fallback: 候选 feishu:oc_private_xxx(创建者私聊)
550
+
551
+ - [启用] a19f... | 昨日总结 | 0 0 9 * * 1-5
552
+ text: 当我们每天第一次沟通,记得给我发昨日总结
553
+ target: feishu:oc_group_yyy(原会话已迁移到 feishu:oc_group_zzz) | session: ses_yyy
554
+ orphan: 是(原会话已迁移到其他窗口)
555
+ fallback: 候选 feishu:oc_private_xxx(创建者私聊);原会话已迁移,运行时不会直接回退
556
+ ```
557
+
558
+ ```bash
559
+ # 列出任务
560
+ curl http://127.0.0.1:4097/cron/list
561
+
562
+ # 新增任务(每分钟触发 systemEvent)
563
+ curl -X POST http://127.0.0.1:4097/cron/add \
564
+ -H "Content-Type: application/json" \
565
+ -d '{
566
+ "name": "daily-check",
567
+ "schedule": { "kind": "cron", "expr": "0 * * * * *" },
568
+ "payload": {
569
+ "kind": "systemEvent",
570
+ "text": "执行例行检查",
571
+ "sessionId": "ses_xxx",
572
+ "delivery": {
573
+ "platform": "feishu",
574
+ "conversationId": "oc_xxx"
575
+ }
576
+ },
577
+ "enabled": true
578
+ }'
579
+
580
+ # 更新任务(禁用)
581
+ curl -X POST http://127.0.0.1:4097/cron/update \
582
+ -H "Content-Type: application/json" \
583
+ -d '{
584
+ "id": "<job-id>",
585
+ "enabled": false
586
+ }'
587
+
588
+ # 删除任务
589
+ curl -X POST http://127.0.0.1:4097/cron/remove \
590
+ -H "Content-Type: application/json" \
591
+ -d '{ "id": "<job-id>" }'
592
+ ```
593
+
594
+ 如果配置了 `RELIABILITY_CRON_API_TOKEN`,请求需携带:
595
+
596
+ ```bash
597
+ -H "Authorization: Bearer <token>"
598
+ ```
599
+
600
+ ### 3) 最小可用配置(.env)
601
+
602
+ ```dotenv
603
+ # 建议保持本地 OpenCode,才能触发自动救援
604
+ OPENCODE_HOST=localhost
605
+ OPENCODE_PORT=4096
606
+
607
+ # Cron 基础开关
608
+ RELIABILITY_CRON_ENABLED=true
609
+ RELIABILITY_CRON_API_ENABLED=true
610
+ RELIABILITY_CRON_API_HOST=127.0.0.1
611
+ RELIABILITY_CRON_API_PORT=4097
612
+ # RELIABILITY_CRON_API_TOKEN=your-token
613
+ # RELIABILITY_CRON_JOBS_FILE=/absolute/path/jobs.json
614
+ # RELIABILITY_CRON_ORPHAN_AUTO_CLEANUP=false
615
+ # RELIABILITY_CRON_FORWARD_TO_PRIVATE=false
616
+ # RELIABILITY_CRON_FALLBACK_FEISHU_CHAT_ID=oc_xxx
617
+ # RELIABILITY_CRON_FALLBACK_DISCORD_CONVERSATION_ID=1234567890
618
+
619
+ # 主动心跳开关(默认关闭)
620
+ RELIABILITY_PROACTIVE_HEARTBEAT_ENABLED=false
621
+ RELIABILITY_INBOUND_HEARTBEAT_ENABLED=false
622
+
623
+ # 可靠性策略(默认即已生效,这里是显式写法)
624
+ RELIABILITY_LOOPBACK_ONLY=true
625
+ RELIABILITY_HEARTBEAT_INTERVAL_MS=1800000
626
+ RELIABILITY_FAILURE_THRESHOLD=3
627
+ RELIABILITY_WINDOW_MS=90000
628
+ RELIABILITY_COOLDOWN_MS=300000
629
+ RELIABILITY_REPAIR_BUDGET=3
630
+
631
+ # 心跳 Agent 与提示词(可选)
632
+ # RELIABILITY_HEARTBEAT_AGENT=companion
633
+ # RELIABILITY_HEARTBEAT_PROMPT=Read HEARTBEAT.md ... reply HEARTBEAT_OK
634
+
635
+ # 心跳异常时推送到飞书 chat_id(逗号分隔,可选)
636
+ # RELIABILITY_HEARTBEAT_ALERT_CHATS=oc_xxx,oc_yyy
637
+
638
+ # 宕机救援会读取并备份这个配置文件
639
+ OPENCODE_CONFIG_FILE=./opencode.json
640
+ ```
641
+
642
+ ### 4) 心跳怎么用
643
+
644
+ 0. 若要启用主动心跳,先设置 `RELIABILITY_PROACTIVE_HEARTBEAT_ENABLED=true` 并重启服务。
645
+ 1. 打开 `HEARTBEAT.md`,按以下规则编辑检查项:
646
+ - `- [ ] failure_type: 描述` = 启用
647
+ - `- [x] failure_type: 描述` = 停用
648
+ 2. Bridge 定时器按 `RELIABILITY_HEARTBEAT_INTERVAL_MS` 触发,主动向 Agent Session 发送心跳提示。
649
+ 3. Agent 读取 `HEARTBEAT.md` 并执行检查:
650
+ - 无异常:回复 `HEARTBEAT_OK`
651
+ - 有异常:返回告警文本(可由桥接推送到 `RELIABILITY_HEARTBEAT_ALERT_CHATS`)
652
+ 4. 查看 `memory/heartbeat-session.json`(心跳 session)与 `logs/reliability-audit.jsonl`(审计)。
653
+
654
+ ### 5) 执行流程(MD)
655
+
656
+ #### 5.1 Cron 执行流程
657
+
658
+ ```mermaid
659
+ flowchart TD
660
+ A[Bridge 启动] --> B[加载内置 Cron 任务]
661
+ B --> C[加载持久化 jobs.json]
662
+ C --> D[注册到 CronScheduler]
663
+ D --> E[按 cron expr 定时触发]
664
+ E --> F{payload.kind}
665
+ F -->|systemEvent| G[检查原聊天窗口与原会话绑定]
666
+ G -->|绑定仍有效| H[在原 OpenCode 会话执行]
667
+ H --> I[结果回推原聊天窗口]
668
+ G -->|原窗口失效且允许转发| J[执行后转发到私聊/备用窗口]
669
+ G -->|原窗口失效且禁止转发| K[跳过或清理僵尸任务]
670
+ ```
671
+
672
+ #### 5.2 心跳执行流程
673
+
674
+ ```mermaid
675
+ flowchart TD
676
+ A[Bridge 定时器每 N 分钟] --> B[发送心跳提示到 Agent Session]
677
+ B --> C[Agent 读取 HEARTBEAT.md]
678
+ C --> D{检查结果}
679
+ D -->|无异常| E[回复 HEARTBEAT_OK]
680
+ D -->|有异常| F[回复告警内容]
681
+ E --> G[桥接静默记录]
682
+ F --> H[桥接记录并可推送用户告警]
683
+ ```
684
+
685
+ ### 6) 自动救援触发条件
686
+
687
+ 当前运行链路按“无限重连阈值”判定:
688
+
689
+ - 健康探针持续失败,且满足:
690
+ - 连续失败次数 `>= RELIABILITY_FAILURE_THRESHOLD`
691
+ - 失败窗口时长 `>= RELIABILITY_WINDOW_MS`
692
+ - 同时满足以下守卫:
693
+ - 目标主机为 loopback(`localhost/127.0.0.1/::1`)
694
+ - 修复预算未耗尽
695
+ - 距离上次修复已过冷却窗口
696
+
697
+ 命中后会执行:加锁与单实例检查 → 环境诊断 → 配置备份与两级回退 → 启动 OpenCode → 健康复检 → 自动下发修复上下文。
698
+
699
+ ### 7) 产物与审计位置
700
+
701
+ - 心跳 session 状态:`memory/heartbeat-session.json`
702
+ - 可靠性审计:`logs/reliability-audit.jsonl`
703
+ - 配置备份:`<OPENCODE_CONFIG_FILE>.bak.<timestamp>.<sha256>`
704
+ - 恢复通知:自动发送到 OpenCode 会话,消息内包含 `failureReason`、`backupPath`、`nextAction`
705
+
706
+ ### 8) 僵尸 Cron 与回退策略
707
+
708
+ - `RELIABILITY_CRON_ORPHAN_AUTO_CLEANUP=false`:
709
+ - 不在启动时自动扫描 Cron 孤儿任务。
710
+ - 不在飞书群解散 / Discord 频道删除时自动删除对应 Cron。
711
+ - 任务执行时若绑定失效,会直接跳过并记录日志。
712
+ - `RELIABILITY_CRON_ORPHAN_AUTO_CLEANUP=true`:
713
+ - 启动时扫描并删除缺少原窗口绑定或缺少原 session 的僵尸 Cron。
714
+ - 飞书群解散、Discord 频道删除时,联动删除绑定到该窗口的 Cron。
715
+ - `stale-cleanup` 周期任务也会继续扫描僵尸 Cron。
716
+ - `RELIABILITY_CRON_FORWARD_TO_PRIVATE=true`:
717
+ - 当原聊天窗口失效、但原 session 仍可执行且未绑定到别的活动窗口时,可把结果转发到私聊/备用窗口。
718
+ - 备用目标优先级:任务显式 fallback > env fallback id > 同平台创建者私聊。
719
+
720
+ ### 9) 常用自检命令
721
+
722
+ ```bash
723
+ # 1) 检查 OpenCode 本地环境
724
+ node scripts/deploy.mjs opencode-check
725
+
726
+ # 2) 核验可靠性启动/清理链路
727
+ npm test -- tests/reliability-bootstrap.test.ts
728
+
729
+ # 3) 核验救援端到端场景
730
+ npm test -- tests/reliability-rescue.e2e.test.ts
731
+ ```
732
+
733
+ 补充:`RELIABILITY_MODE` 目前是预留策略字段,当前版本仍以“阈值 + 预算 + 冷却 + loopback 限制”作为实际触发条件。
734
+
735
+ ### 🎮 Discord 接入说明(v2.9.0-beta)
736
+
737
+ 启用 Discord 需要同时满足:
738
+
739
+ 1. `.env` 中开启 `DISCORD_ENABLED=true`
740
+ 2. 配置 `DISCORD_TOKEN`(或 `DISCORD_BOT_TOKEN`)
741
+ 3. 建议配置 `DISCORD_CLIENT_ID`
742
+ 4. 在 Discord Developer Portal 中开启 **Message Content Intent**
743
+
744
+ 推荐同时配置:
745
+
746
+ - `ENABLED_PLATFORMS=feishu,discord`(显式控制启用平台)
747
+ - `GROUP_REQUIRE_MENTION=true`(降低群聊噪声,只在明确 @ 机器人时响应)
748
+
749
+ 当前 Discord 侧的可用能力:
750
+
751
+ - 频道/私聊消息接入与自动会话绑定
752
+ - 文本问答闭环(请求 OpenCode 后回帖)
753
+ - 流式输出展示:分隔符 + 思维链路代码块 + 最终答复正文
754
+ - OpenCode 会话命名:`Discord 私聊/群聊 <ID前6位> <频道ID前6位>`
755
+ - **权限交互闭环**:支持 Button/Select 组件交互,同时提供文本兜底(回复"允许/拒绝/始终允许")
756
+ - question 交互闭环(显示题干 + 下拉作答 + 文本自定义答案 + 跳过本题)
757
+ - 机器人消息发送、回复、编辑、删除
758
+ - 会话频道创建:`///new-channel`(频道名 `opencode{sessionID前6位(去前缀)}`,权限不足自动回退当前频道绑定)
759
+ - 频道删除自动解绑:监听 `ChannelDelete` 自动清理本地会话映射
760
+ - 频道删除自动销毁会话:仅对 Discord 新建会话生效;外部绑定会话受保护不删除
761
+ - 未绑定自动建会话 onboarding:首次消息自动创建会话并发送帮助引导,同时提示`当前会话未与opencode绑定,已新建会话并绑定如需切换请按照help提示操作`
762
+ - 频道命令:`///session`、`///new`、`///new-channel`、`///bind`、`///unbind`、`///rename`、`///sessions`、`///workdir`、`///send`、`///clear`
763
+ - 下拉控制面板:`///create_chat`(同卡片多下拉:会话/模型/角色;强度使用命令行)
764
+
765
+ 平台边界原则(不跨平台借调):
766
+
767
+ - Discord 与 Feishu 是独立平台,各自保持原生交互范式
768
+ - Discord 侧不硬复制"飞书式卡片工作流",而是利用 Discord 原生组件(Button/Select)逐步演进
769
+ - 两端会话体系独立,不支持跨平台会话借调或 UI 组件复用
770
+
771
+
772
+ ## ⚙️ 飞书后台配置
773
+
774
+ 建议使用长连接模式(WebSocket 事件)。
775
+
776
+ ### 事件订阅(按代码已注册项)
777
+
778
+ | 事件 | 必需 | 用途 |
779
+ |---|---|---|
780
+ | `im.message.receive_v1` | 是 | 接收群聊/私聊消息 |
781
+ | `im.message.recalled_v1` | 是 | 用户撤回触发 `/undo` 回滚 |
782
+ | `im.chat.member.user.deleted_v1` | 是 | 成员退群后触发生命周期清理 |
783
+ | `im.chat.disbanded_v1` | 是 | 群解散后清理本地会话映射 |
784
+ | `card.action.trigger` | 是 | 处理控制面板、权限确认、提问卡片回调 |
785
+ | `im.message.message_read_v1` | 否 | 已读回执兼容(可不开启) |
786
+
787
+ ### 应用权限(按实际调用接口梳理)
788
+
789
+ | 能力分组 | 代码中调用的接口 | 用途 |
790
+ |---|---|---|
791
+ | 消息读写与撤回(`im:message`) | `im:message.p2p_msg:readonly` / `im:message.group_at_msg:readonly` / `im:message.group_msg` / `im:message.reactions:read` / `im:message.reactions:write_only` | 发送文本/卡片、流式更新卡片、撤回消息 |
792
+ | 群与成员管理(`im:chat`) | `im:chat.members:read` / `im:chat.members:write_only` | 私聊建群、拉人进群、查群成员、自动清理无效群 |
793
+ | 消息资源下载(`im:resource`) | `im.messageResource.get` | 下载图片/文件附件并转发给 OpenCode |
794
+
795
+ 注意:飞书后台不同版本的权限名称可能略有差异,按上表接口能力逐项对齐即可;若只需文本对话且不处理附件,可暂不开启 `im:resource`。
796
+ - 可以复制下方参数保存至acc.json,然后在飞书`开发者后台`--`权限管理`--`批量导入/导出权限`
797
+ ```json
798
+ {
799
+ "scopes": {
800
+ "tenant": [
801
+ "im:message.p2p_msg:readonly",
802
+ "im:chat",
803
+ "im:chat.members:read",
804
+ "im:chat.members:write_only",
805
+ "im:message",
806
+ "im:message.group_at_msg:readonly",
807
+ "im:message.group_msg",
808
+ "im:message.reactions:read",
809
+ "im:message.reactions:write_only",
810
+ "im:resource"
811
+ ],
812
+ "user": []
813
+ }
814
+ }
815
+ ```
816
+
817
+ <a id="命令速查"></a>
818
+ ## 📖 命令速查
819
+
820
+ | 命令 | 说明 |
821
+ |---|---|
822
+ | `/help` | 查看帮助 |
823
+ | `/panel` | 打开控制面板(模型、角色、强度状态、停止、撤回) |
824
+ | `/model` | 查看当前模型 |
825
+ | `/model <provider:model>` | 切换模型(支持 `provider/model`) |
826
+ | `/effort` | 查看当前会话推理强度与当前模型可选档位 |
827
+ | `/effort <档位>` | 设置会话默认强度(支持 `none/minimal/low/medium/high/max/xhigh`) |
828
+ | `/effort default` | 清除会话强度,回到模型默认策略 |
829
+ | `/fast` `/balanced` `/deep` | 强度快捷命令(分别映射 `low/high/xhigh`) |
830
+ | `/agent` | 查看当前 Agent |
831
+ | `/agent <name>` | 切换 Agent |
832
+ | `/agent off` | 关闭 Agent,回到默认 |
833
+ | `/role create <规格>` | 斜杠形式创建自定义角色 |
834
+ | `创建角色 名称=...; 描述=...; 类型=...; 工具=...` | 自然语言创建自定义角色并切换 |
835
+ | `/stop` | 中断当前会话执行 |
836
+ | `/undo` | 撤回上一轮交互(OpenCode + 飞书同步) |
837
+ | `/sessions` | 列出当前项目会话(含未绑定与仅本地映射记录) |
838
+ | `/sessions all` | 列出所有项目的全部会话 |
839
+ | `/session new` | 开启新话题(重置上下文,使用默认项目) |
840
+ | `/session new <项目别名或绝对路径>` | 在指定项目/目录中新建会话 |
841
+ | `/session new --name <名称>` | 创建会话时直接命名(如 `/session new --name 技术架构评审`) |
842
+ | `/rename <新名称>` | 随时重命名当前会话(如 `/rename Q3后端API设计讨论`) |
843
+ | `/project list` | 列出可用项目(别名 + 历史目录) |
844
+ | `/project default` | 查看当前群默认项目 |
845
+ | `/project default set <路径或别名>` | 设置当前群的默认工作项目 |
846
+ | `/project default clear` | 清除当前群默认项目 |
847
+ | `/session <sessionId>` | 手动绑定已有 OpenCode 会话(支持 Web 端创建的跨工作区会话;需启用 `ENABLE_MANUAL_SESSION_BIND`) |
848
+ | `新建会话窗口` | 自然语言触发新建会话(等价 `/session new`) |
849
+ | `/clear` | 等价于 `/session new` |
850
+ | `/clear free session` / `/clear_free_session` | 手动触发一次与启动清理同规则的兜底扫描,并顺带清理僵尸 Cron |
851
+ | `/clear free session <sessionId>` / `/clear_free_session <sessionId>` | 删除指定 OpenCode 会话,并移除所有本地绑定映射与该会话绑定的 Cron |
852
+ | `/compact` | 调用 OpenCode summarize,压缩当前会话上下文 |
853
+ | `!<shell命令>` | 透传白名单 shell 命令(如 `!ls`、`!pwd`、`!mkdir`、`!git status`) |
854
+ | `/create_chat` / `/建群` | 私聊中调出建群卡片(下拉选择后点击"创建群聊"生效) |
855
+ | `/send <绝对路径>` | 发送指定路径的文件到当前群聊 |
856
+ | `/restart opencode` | 重启本地 OpenCode 进程(仅 loopback) |
857
+ | `/status` | 查看当前群绑定状态 |
858
+
859
+ Discord 侧推荐命令(优先 `///` 前缀,避免与原生 Slash 冲突):
860
+ | 命令 | 说明 |
861
+ |---|---|
862
+ | `///session` | 查看当前频道绑定的 OpenCode 会话 |
863
+ | `///new [可选名称] [--dir 路径` / `别名]` | 新建并绑定会话 |
864
+ | `///new-channel [可选名称] [--dir 路径` / `别名]` | 新建会话频道并绑定 |
865
+ | `///bind <sessionId>` | 绑定已有会话 |
866
+ | `///unbind` | 仅解绑当前频道会话 |
867
+ | `///rename <新名称>` | 重命名当前会话 |
868
+ | `///sessions` | 查看最近可绑定会话 |
869
+ | `///effort` | 查看当前强度 |
870
+ | `///effort <档位>` | 设置会话默认强度(按当前模型能力校验) |
871
+ | `///effort default` | 清除会话强度 |
872
+ | `///workdir [路径 ` / `别名 ` / `clear]` | 设置/查看默认工作目录 |
873
+ | `///undo` | 回撤上一轮 |
874
+ | `///compact` / `///compat` | 压缩上下文 |
875
+ | `///send <绝对路径>` | 发送白名单文件到当前频道 |
876
+ | `发送文件 <绝对路径>` | 中文自然语言触发发送白名单文件 |
877
+ | `///restart opencode` | 重启本地 OpenCode 进程(仅 loopback) |
878
+ | `///clear` | 删除并解绑当前频道会话 |
879
+ | `///create_chat` | 打开下拉会话控制面板(查看状态/新建/绑定/模型/角色/回撤/压缩) |
880
+ | `///create_chat model <页码>` | 打开模型分页面板(总容量最多 500,单页 24) |
881
+ | `///create_chat session` / `agent` / `effort` | 打开分类面板 |
882
+
883
+ 说明:
884
+
885
+ - 已保留兼容命令:`/session`、`/new`、`/new-session`、`/clear`。
886
+ - `///create_chat` 使用 Discord 下拉菜单与弹窗(Modal),用于补齐会话控制体验。
887
+ - `///clear` 在会话频道(topic 带 `oc-session:`)中会尝试直接删除频道;若权限不足则只解绑。
888
+
889
+ - `!` 透传仅支持白名单命令;`vi`/`vim`/`nano` 等交互式编辑器不会透传。
890
+ - 单条临时覆盖可在消息开头使用 `#low` / `#high` / `#max` / `#xhigh`(仅当前条生效)。
891
+ - 强度优先级:`#临时覆盖` > `///effort 会话默认` > 模型默认。
892
+ - `///sessions` 列表列顺序固定为:`工作区目录 | SessionID | OpenCode侧会话名称 | 绑定群明细 | 当前会话状态`。
893
+ - `///create_chat` 下拉标签顺序固定为:`工作区 / Session短ID / 简介`,并按工作区聚合展示。
894
+
895
+ <a id="Agent(角色)使用"></a>
896
+ ## 🤖 Agent(角色)使用
897
+
898
+ ### 1) 查看与切换
899
+
900
+ - 推荐使用 `/panel` 可视化切换角色(当前群即时生效)。
901
+ - 也可用命令:`/agent`(查看当前)、`/agent <name>`(切换)、`/agent off`(回到默认)。
902
+
903
+ ### 2) 自定义 Agent
904
+
905
+ - 支持自然语言直接创建并切换:
906
+
907
+ ```text
908
+ 创建角色 名称=旅行助手; 描述=擅长制定旅行计划; 类型=主; 工具=webfetch; 提示词=先询问预算和时间,再给三套方案
909
+ ```
910
+
911
+ - 也支持斜杠形式:
912
+
913
+ ```text
914
+ /role create 名称=代码审查员; 描述=关注可维护性和安全; 类型=子; 工具=read,grep; 提示词=先列风险,再给最小改动建议
915
+ ```
916
+
917
+ - `类型` 支持 `主/子`(或 `primary/subagent`)。
918
+
919
+ ### 3) 配置 Agent(提醒)
920
+
921
+ - 配置后如果 `/panel` 未立即显示新角色,重启 OpenCode 即可。
922
+
923
+ <a id="关键实现细节"></a>
924
+ ## 📌 关键实现细节
925
+
926
+ ### 1) 权限请求回传
927
+
928
+ - `permission.asked` 里 `tool` 可能不是字符串工具名,实际白名单匹配可落在 `permission` 字段。
929
+ - 回传接口要求 `response` 为 `once | always | reject`,不是 `allow | deny`。
930
+
931
+ ### 2) question 工具交互
932
+
933
+ - 问题渲染为飞书卡片,答案通过用户文字回复解析。
934
+ - 解析后按 OpenCode 需要的 `answers: string[][]` 回传,并纳入撤回历史。
935
+
936
+ ### 3) 流式与思考卡片
937
+
938
+ - 文本与思考分流写入输出缓冲;出现思考内容时自动切换卡片模式。
939
+ - 卡片支持展开/折叠思考,最终态保留完成状态。
940
+
941
+ ### 4) `/undo` 一致性
942
+
943
+ - 需要同时删除飞书侧消息并对 OpenCode 执行 `revert`。
944
+ - 问答场景可能涉及多条关联消息,使用递归回滚兜底。
945
+
946
+ ### 5) 私聊建群卡片交互
947
+
948
+ - 下拉选择动作仅记录会话选择,不依赖卡片重绘;行为与 `/panel` 的下拉交互保持一致。
949
+ - 点击“创建群聊”时才执行建群与绑定,避免因卡片状态同步导致误绑定。
950
+
951
+ ### 6) `/clear free session` 行为
952
+
953
+ - 该命令不做单独清理规则,而是复用生命周期扫描逻辑。
954
+ - 可在不重启进程时,手动触发一次“启动时清理”的同规则兜底扫描。
955
+
956
+ ### 7) 文件发送到飞书
957
+
958
+ - `/send <绝对路径>` 直接调用飞书上传 API,不经过 AI,0 延迟。
959
+ - 图片(.png/.jpg/.gif/.webp 等)走图片通道(上限 10MB),其余走文件通道(上限 30MB),与飞书官方限制一致。
960
+ - 内置敏感文件黑名单(.env、id_rsa、.pem 等),防止误发。
961
+ - **安全策略**:仅允许发送位于 `ALLOWED_DIRECTORIES` 白名单范围内的文件;未配置 `ALLOWED_DIRECTORIES` 时,`/send` 默认拒绝。
962
+
963
+
964
+ ### 8) 工作目录策略(DirectoryPolicy)
965
+
966
+ - 所有会话创建入口统一走 `DirectoryPolicy.resolve()` 9 阶段校验流水线。
967
+ - 校验顺序:优先级合并 → 格式校验 → 路径规范化 → 危险路径拦截 → 白名单校验 → 存在性预检 → realpath 解析 → Git 根目录归一化 → 归一后复检。
968
+ - 安全默认:未配置 `ALLOWED_DIRECTORIES` 时,用户不能自定义路径。
969
+ - 错误信息脱敏:用户侧只看到通用提示,完整路径仅写入服务端日志。
970
+ - 目录优先级:显式指定 > 项目别名 > 群默认 > 全局默认 > OpenCode 服务端默认。
971
+ ## 🚀 灰度部署与回滚 SOP
972
+
973
+ > **注意**: 本章节适用于 v2.9.0-beta 版本,涉及路由器模式的灰度升级流程。
974
+
975
+ ### 1. 路由器模式配置
976
+
977
+ #### 1.1 模式说明
978
+
979
+ | 模式 | 说明 | 适用场景 | 风险等级 |
980
+ |------|------|----------|----------|
981
+ | `legacy` | 旧版直通路由 | 默认模式,稳定生产部署 | 🟢 低 |
982
+ | `dual` | 双轨模式(日志对比) | 灰度测试阶段,记录新旧路由对比 | 🟡 中 |
983
+ | `router` | 新版根路由器 | 验证通过后的全量模式 | 🟢 低 |
984
+
985
+ #### 1.2 配置方式
986
+
987
+ ```bash
988
+ # 临时设置(命令行)
989
+ ROUTER_MODE=legacy node scripts/start.mjs
990
+ ROUTER_MODE=dual node scripts/start.mjs
991
+ ROUTER_MODE=router node scripts/start.mjs
992
+
993
+ # 永久设置(.env 文件)
994
+ echo "ROUTER_MODE=dual" >> .env
995
+ ```
996
+
997
+ #### 1.3 启动日志示例
998
+
999
+ **Legacy 模式:**
1000
+ ```
1001
+ [Config] 路由器模式: legacy
1002
+ ```
1003
+
1004
+ **Dual 模式:**
1005
+ ```
1006
+ [Config] 路由器模式: dual
1007
+ [Config] ⚠️ 双轨模式: 将记录新旧路由对比日志,不改变当前行为
1008
+ [Config] 📝 如需回滚到旧版路由,设置 ROUTER_MODE=legacy 并重启服务
1009
+ ```
1010
+
1011
+ **Router 模式:**
1012
+ ```
1013
+ [Config] 路由器模式: router
1014
+ ```
1015
+
1016
+ ### 2. 灰度验收流程
1017
+
1018
+ #### 2.1 三阶段验收
1019
+
1020
+ 遵严格的三阶段验证流程,确保回滚路径清晰可控:
1021
+
1022
+ ```mermaid
1023
+ flowchart LR
1024
+ A[Legacy 模式] -->|启动验证| B[Dual 模式]
1025
+ B -->|观察 24h| C[Router 模式]
1026
+ C -->|无异常| D[全量上线]
1027
+ C -->|异常| E[立即回滚]
1028
+ B -->|异常| E
1029
+ ```
1030
+
1031
+ **Phase 1: Legacy 验证**
1032
+ - 配置: `ROUTER_MODE=legacy`
1033
+ - 验证内容: 基础消息流、权限流、卡片流
1034
+ - 通过标准: 53 个单元测试 100% 通过
1035
+
1036
+ **Phase 2: Dual 验证**
1037
+ - 配置: `ROUTER_MODE=dual`
1038
+ - 验证内容: 双轨日志对比、行为一致性
1039
+ - 关键日志: `type: "[Router][dual]"` 字段完整性
1040
+ - 观察时间: ≥ 24 小时
1041
+
1042
+ **Phase 3: Router 验证**
1043
+ - 配置: `ROUTER_MODE=router`
1044
+ - 验证内容: 新路由事件分发、功能等价性
1045
+ - 通过标准: 与 legacy 模式行为一致
1046
+
1047
+ #### 2.2 验证套件
1048
+
1049
+ **功能验证:**
1050
+ - [ ] 私聊消息收发
1051
+ - [ ] 群聊消息收发
1052
+ - [ ] 权限卡片确认
1053
+ - [ ] 提问卡片处理
1054
+ - [ ] 消息撤回同步
1055
+ - [ ] 会话绑定迁移
1056
+
1057
+ **性能验证:**
1058
+ - [ ] 消息延迟 < 500ms
1059
+ - [ ] 错误率 < 0.1%
1060
+ - [ ] 卡片.update成功率 > 99%
1061
+
1062
+ **日志验证:**
1063
+ - [ ] 双轨日志字段完整
1064
+ - [ ] 无异常错误输出
1065
+
1066
+ ### 3. 回滚 SOP
1067
+
1068
+ #### 3.1 回滚触发条件
1069
+
1070
+ 出现以下任一情况时,立即执行回滚:
1071
+
1072
+ | 触发条件 | 响应级别 | 说明 |
1073
+ |----------|----------|------|
1074
+ | 消息延迟 > 2s | P0 | 严重影响用户体验 |
1075
+ | 错误率 > 5% | P0 | 系统异常率过高 |
1076
+ | 权限卡/提问卡失效 | P0 | 功能严重降级 |
1077
+ | 会话绑定失败率 > 10% | P1 | 影响多会话管理 |
1078
+
1079
+ #### 3.2 回滚步骤
1080
+
1081
+ ```bash
1082
+ # 1. 停止服务
1083
+ node scripts/stop.mjs
1084
+
1085
+ # 2. 设置回滚模式
1086
+ echo "ROUTER_MODE=legacy" > .env
1087
+
1088
+ # 3. 重启服务
1089
+ node scripts/start.mjs
1090
+
1091
+ # 4. 验证回滚成功
1092
+ grep "路由器模式" logs/service.log
1093
+ # 期望输出: [Config] 路由器模式: legacy
1094
+ ```
1095
+
1096
+ #### 3.3 回滚后复测
1097
+
1098
+ 回滚后必须验证:
1099
+
1100
+ - [ ] 普通消息收发正常
1101
+ - [ ] 权限卡片正确显示
1102
+ - [ ] 提问卡片正确处理
1103
+ - [ ] 撤回操作同步
1104
+ - [ ] 会话绑定功能正常
1105
+
1106
+ ### 4. 日志诊断
1107
+
1108
+ #### 4.1 双轨日志格式(dual 模式)
1109
+
1110
+ ```json
1111
+ {
1112
+ "type": "[Router][dual]",
1113
+ "event": "onMessage",
1114
+ "platform": "feishu",
1115
+ "conversationKey": "feishu:chat_id_xxx",
1116
+ "sessionId": "session_id_xxx",
1117
+ "routeDecision": "group",
1118
+ "chatType": "group",
1119
+ "chatId": "chat_id_xxx"
1120
+ }
1121
+ ```
1122
+
1123
+ **字段说明:**
1124
+ - `conversationKey`: 会话键(格式: `{platform}:{chatId}`)
1125
+ - `sessionId`: OpenCode 会话 ID
1126
+ - `routeDecision`: 路由决策(p2p/group/card_action/opencode_event)
1127
+
1128
+ #### 4.2 关键日志命令
1129
+
1130
+ ```bash
1131
+ # 检查路由器模式
1132
+ grep "路由器模式" logs/service.log
1133
+
1134
+ # 检查双轨日志(dual 模式)
1135
+ grep "\[Router\]\[dual\]" logs/service.log
1136
+
1137
+ # 检查错误日志
1138
+ tail -n 100 logs/service.err | grep -i error
1139
+ ```
1140
+
1141
+ ### 5. 环境变量参考
1142
+
1143
+ | 变量 | 默认值 | 说明 |
1144
+ |------|--------|------|
1145
+ | `ROUTER_MODE` | `legacy` | 路由器模式: legacy \| dual \| router |
1146
+ | `ENABLED_PLATFORMS` | * | 启用的平台列表(逗号分隔) |
1147
+
1148
+ **注意**: `ROUTER_MODE` 仅接受 `legacy`、`dual`、`router` 三个值,其他值将回退到 `legacy`。
1149
+
1150
+ ### 6. 相关文档
1151
+
1152
+ | 文档路径 | 说明 |
1153
+ |----------|------|
1154
+ | `.sisyphus/evidence/task-16-rollout-gate.txt` | 三阶段验收证据 |
1155
+ | `.sisyphus/evidence/task-16-fallback-recovery.txt` | 详细回滚 SOP |
1156
+ | `src/config.ts` | 路由器模式配置实现 |
1157
+ | `src/router/root-router.ts` | 根路由器实现 |
1158
+
1159
+ ---
1160
+
1161
+ <a id="关键实现细节"></a>
1162
+
1163
+ ## 🛠️ 故障排查
1164
+
1165
+ | 现象 | 优先检查 |
1166
+ |---|---|
1167
+ | 飞书发送消息后OpenCode无反应 | 仔细检查飞书权限;确认 [飞书后台配置](#飞书后台配置) 正确 |
1168
+ | 点权限卡片后 OpenCode 无反应 | 日志是否出现权限回传失败;确认回传值是 `once/always/reject` |
1169
+ | 权限卡或提问卡发不到群 | `.chat-sessions.json` 中 `sessionId -> chatId` 映射是否存在 |
1170
+ | 卡片更新失败 | 消息类型是否匹配;失败后是否降级为重发卡片 |
1171
+ | `/compact` 失败 | OpenCode 可用模型是否正常;必要时先 `/model <provider:model>` 再重试 |
1172
+ | `!ls` 等 shell 命令失败 | 当前会话 Agent 是否可用;可先执行 `/agent general` 再重试 |
1173
+ | 后台模式无法停止 | `logs/bridge.pid` 是否残留;使用 `node scripts/stop.mjs` 清理 |
1174
+ | 心跳似乎没有执行 | 检查 `HEARTBEAT.md` 是否把检查项标记为 `- [ ]`;检查 `memory/heartbeat-state.json` 的 `lastRunAt` 是否更新 |
1175
+ | 自动救援没有触发 | 检查 `OPENCODE_HOST` 是否为 loopback、`RELIABILITY_LOOPBACK_ONLY` 是否开启、失败次数/窗口是否达到阈值 |
1176
+ | 自动救援被拒绝(manual) | 检查 `logs/reliability-audit.jsonl` 的 `reason` 字段(常见:`loopback_only_blocked`、`repair_budget_exhausted`) |
1177
+ | 找不到备份配置 | 检查 `logs/reliability-audit.jsonl` 的 `backupPath`,备份文件命名为 `.bak.<timestamp>.<sha256>` |
1178
+ | 私聊首次会推送多条引导消息 | 这是首次流程(建群卡片 + `/help` + `/panel`);后续会按已绑定会话正常对话 |
1179
+ | `/send <路径>` 报"文件不存在" | 确认路径正确且为绝对路径;Windows 路径用 `\` 或 `/` 均可 |
1180
+ | `/send` 报"拒绝发送敏感文件" | 内置安全黑名单拦截了 .env、密钥等敏感文件 |
1181
+ | 文件发送失败提示大小超限 | 飞书图片上限 10MB、文件上限 30MB;压缩后重试 |
1182
+ | OpenCode 大于 `v1.2.15 `版本 通过飞书发消息无不响应 | 检查`~/.config/opencode/opencode.json(linux/mac为config.json)`是否有 ` "default_agent": "companion"`有请删除 |
1183
+ <a id="许可证"></a>
1184
+ ## 📝 许可证
1185
+
1186
+ 本项目采用 [GNU General Public License v3.0](LICENSE)
1187
+
1188
+ **GPL v3 意味着:**
1189
+ - ✅ 可自由使用、修改和分发
1190
+ - ✅ 可用于商业目的
1191
+ - 📝 必须开源修改版本
1192
+ - 📝 必须保留原作者版权
1193
+ - 📝 衍生作品必须使用 GPL v3 协议
1194
+
1195
+ 如果这个项目对你有帮助,请给个 ⭐️ Star!