opencode-feishu 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,79 +1,127 @@
1
1
  # OpenCode 飞书插件
2
2
 
3
- 独立运行的飞书机器人服务,通过飞书 WebSocket 长连接与本地 [OpenCode](https://github.com/anomalyco/opencode) Server 对接,在飞书内与 AI 对话,并支持流式回复与实时占位更新。本插件仅作为**消息中继**:所有消息(包括以 `/` 开头的内容)原样转发给 OpenCode,不解析命令、不选择模型或 Agent。无需公网服务器即可完成从零到可用的对接。
3
+ **opencode-feishu** [OpenCode](https://github.com/anomalyco/opencode) 的官方飞书插件(不是独立服务),通过飞书 WebSocket 长连接将飞书消息接入 OpenCode AI 对话。插件作为**消息中继**:所有消息(包括以 `/` 开头的命令)原样转发给 OpenCode,不解析命令、不选择模型或 Agent
4
4
 
5
- **主要能力:**
5
+ npm 包地址:[https://www.npmjs.com/package/opencode-feishu](https://www.npmjs.com/package/opencode-feishu)
6
6
 
7
- - **飞书 WebSocket 长连接**:使用飞书「事件与回调」的「长连接」模式接收消息,无需配置 Webhook 地址。
8
- - **OpenCode 对话**:将飞书消息转为 OpenCode 会话的 prompt,通过轮询 + SSE 事件流获取回复并回写到飞书。
7
+ ---
8
+
9
+ ## 主要能力
10
+
11
+ - **飞书 WebSocket 长连接**:使用飞书「事件与回调」的「长连接」模式接收消息,无需配置公网 Webhook 地址。
12
+ - **OpenCode 对话中继**:将飞书消息转为 OpenCode 会话的 prompt,通过轮询获取回复并回写到飞书。
9
13
  - **群聊静默监听**:始终将群消息转发给 OpenCode 积累上下文,仅在 bot 被直接 @提及时才回复,避免刷屏。
10
- - **入群上下文摄入**:bot 被拉入群聊时,自动读取历史消息作为 OpenCode 对话上下文。
11
- - **SSE 事件流**:实时更新「正在思考…」占位消息,断线自动重连(5 秒间隔)。
14
+ - **入群上下文摄入**:bot 被拉入群聊时,自动读取最近 50 条历史消息作为 OpenCode 对话上下文。
15
+ - **事件流实时更新**:通过插件 `event` 钩子接收 `message.part.updated` 事件,实时更新「正在思考…」占位消息。
12
16
 
13
17
  ---
14
18
 
15
- ## 架构概览
19
+ ## 快速开始
16
20
 
17
- ```mermaid
18
- flowchart LR
19
- FeishuApp["Feishu App (WebSocket)"] --> Gateway["gateway.ts"]
20
- Gateway --> Dedup["dedup.ts"]
21
- Gateway --> GroupFilter["group-filter.ts"]
22
- Gateway -->|"bot.added"| History["history.ts"]
23
- Gateway --> Chat["chat.ts"]
24
- Chat --> SessionMgr["session/manager.ts"]
25
- Chat --> OcClient["opencode/client.ts"]
26
- OcClient --> OpenCodeAPI["OpenCode Server API"]
27
- History --> OcClient
28
- Chat --> Sender["sender.ts"]
29
- SSE["events.ts (SSE)"] --> OcClient
30
- SSE --> Sender
31
- Sender --> FeishuApp
21
+ ### 前置条件
22
+
23
+ - **Node.js** >= 20.0.0
24
+ - **OpenCode**:已安装并能正常使用(参见 [OpenCode 文档](https://github.com/anomalyco/opencode))
25
+ - **飞书自建应用**:在 [飞书开放平台](https://open.feishu.cn/app) 创建应用,获取 App ID 和 App Secret
26
+
27
+ ### 方式一:从 npm 安装(推荐)
28
+
29
+ OpenCode 插件目录下安装:
30
+
31
+ ```bash
32
+ # OpenCode 插件目录(Linux/macOS)
33
+ cd ~/.config/opencode/plugins
34
+
35
+ # 创建插件目录并安装
36
+ mkdir -p opencode-feishu
37
+ cd opencode-feishu
38
+ npm install opencode-feishu
32
39
  ```
33
40
 
34
- - **对话路径**:用户发送任意消息(含以 `/` 开头)→ `chat.ts` 获取/创建 OpenCode 会话、原样发送 prompt,轮询消息列表并在稳定后回写;同时 `events.ts` 订阅 OpenCode SSE,实时更新飞书占位消息。
35
- - **静默监听**:群聊中未被 @提及的消息通过 `noReply: true` 发送给 OpenCode,仅记录上下文但不触发 AI 回复。
36
- - **入群摄入**:`im.chat.member.bot.added_v1` 事件触发后,`history.ts` 拉取群聊历史消息并以 `noReply: true` 注入 OpenCode。
37
- - **事件流**:`events.ts` 与 OpenCode 保持 SSE 连接,收到 `message.part.updated` 等事件时更新对应会话的飞书占位消息,断线后 5 秒重连。
41
+ ### 方式二:从源码构建
38
42
 
39
- ---
43
+ ```bash
44
+ git clone https://github.com/your-org/opencode-feishu.git
45
+ cd opencode-feishu
46
+ npm install
47
+ npm run build
48
+ ```
49
+
50
+ ### 本地开发安装(junction/symlink)
51
+
52
+ 构建完成后,将项目目录链接到 OpenCode 插件目录,避免每次修改都需要重新复制文件。
53
+
54
+ **Windows(使用 junction,无需管理员权限):**
40
55
 
41
- ## 前置条件
56
+ ```powershell
57
+ $source = "D:\path\to\opencode-feishu"
58
+ $target = "$env:USERPROFILE\.config\opencode\plugins\opencode-feishu"
59
+ New-Item -ItemType Junction -Path $target -Target $source
60
+ ```
61
+
62
+ **Linux/macOS(使用 symlink):**
42
63
 
43
- - **Node.js** >= 20(或 Bun >= 1.0)
44
- - **OpenCode Server** 已在本机或可访问的机器上运行,默认地址为 `http://localhost:4096`(参见 [OpenCode 文档](https://github.com/anomalyco/opencode))
45
- - **飞书自建应用**:在 [飞书开放平台](https://open.feishu.cn/app) 创建应用并获取 App ID、App Secret
64
+ ```bash
65
+ ln -s /path/to/opencode-feishu ~/.config/opencode/plugins/opencode-feishu
66
+ ```
67
+
68
+ ### 配置 OpenCode 加载插件
69
+
70
+ 在 OpenCode 配置文件(`~/.config/opencode/opencode.json`)中声明插件:
71
+
72
+ ```json
73
+ {
74
+ "plugin": ["opencode-feishu"]
75
+ }
76
+ ```
77
+
78
+ ### 创建飞书配置文件
79
+
80
+ 在以下路径创建配置文件:
81
+
82
+ ```
83
+ ~/.config/opencode/plugins/feishu.json
84
+ ```
85
+
86
+ 内容示例:
87
+
88
+ ```json
89
+ {
90
+ "appId": "cli_xxxxxxxxxxxx",
91
+ "appSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
92
+ }
93
+ ```
94
+
95
+ 配置文件中必须包含 `appId` 和 `appSecret`,其余字段均有默认值(见[配置说明](#配置说明))。
46
96
 
47
97
  ---
48
98
 
49
- ## 飞书开放平台配置(逐步)
99
+ ## 飞书开放平台配置
50
100
 
51
- ### 4.1 创建/打开应用
101
+ ### 创建或打开应用
52
102
 
53
- 在浏览器打开 [飞书开放平台 - 应用列表](https://open.feishu.cn/app),创建或打开你的自建应用,记下 **App ID** 和 **App Secret**(在「凭证与基础信息」中)。
103
+ 在浏览器打开 [飞书开放平台 - 应用列表](https://open.feishu.cn/app),创建或打开你的自建应用,记下 **App ID** 和 **App Secret**(在「凭证与基础信息」页面)。
54
104
 
55
- ### 4.2 添加机器人能力
105
+ ### 添加机器人能力
56
106
 
57
107
  进入该应用 → 左侧「添加应用能力」→ 添加「机器人」。若已添加则跳过。
58
108
 
59
- ### 4.3 配置事件订阅
109
+ ### 配置事件订阅
60
110
 
61
- - 进入「事件与回调」页面。
62
- - **不需要**填写「请求地址」:本服务使用 **WebSocket 长连接** 接收消息,而非 Webhook。
63
- - 在「事件订阅」中添加以下事件:
111
+ 进入「事件与回调」页面,**不需要**填写「请求地址」:本插件使用 **WebSocket 长连接**接收消息,而非 Webhook。
112
+
113
+ 在「事件订阅」中添加以下事件:
64
114
 
65
115
  | 事件 | 说明 |
66
116
  |------|------|
67
117
  | `im.message.receive_v1` | 接收单聊与群聊消息 |
68
118
  | `im.chat.member.bot.added_v1` | 机器人进群(触发历史上下文摄入) |
69
119
 
70
- - 保存。
71
-
72
- ### 4.4 订阅方式:长连接(必选)
120
+ ### 订阅方式:长连接(必选)
73
121
 
74
- 在「事件与回调」中,将 **订阅方式** 设置为「使用 长连接 接收事件/回调」。若使用 Webhook 模式,本服务无法收到消息。
122
+ 在「事件与回调」中,将**订阅方式**设置为「使用长连接接收事件/回调」。使用 Webhook 模式将无法收到消息。
75
123
 
76
- ### 4.5 配置权限
124
+ ### 配置权限
77
125
 
78
126
  进入「权限管理」,搜索并开通以下权限:
79
127
 
@@ -82,151 +130,111 @@ flowchart LR
82
130
  | `im:message` | 获取与发送单聊、群组消息 |
83
131
  | `im:message:send_as_bot` | 以应用身份发消息 |
84
132
  | `im:chat` | 获取群组信息(群聊场景需要) |
85
- | `im:message:readonly`(群聊消息读取) | 获取群组中所有消息(入群历史摄入需要) |
133
+ | `im:message:readonly` | 获取群组中所有消息(入群历史摄入需要) |
86
134
 
87
135
  保存后若有权限变更,需在「版本管理与发布」中重新发布。
88
136
 
89
- ### 4.6 发布应用
137
+ ### 发布应用
90
138
 
91
- 进入「版本管理与发布」→ 创建版本 → 提交审核 → 发布。自建应用在企业内通常可即时通过。
139
+ 进入「版本管理与发布」→ 创建版本 → 提交审核 → 发布。企业内自建应用通常可即时通过。
92
140
 
93
141
  ---
94
142
 
95
- ## 配置说明
96
-
97
- ### 5.1 环境变量
98
-
99
- 复制项目根目录下的 `.env.example` 为 `.env`,按需填写。所有项均可通过环境变量覆盖配置文件中的同名字段。
100
-
101
- **必填:**
102
-
103
- | 变量 | 说明 |
104
- |------|------|
105
- | `FEISHU_APP_ID` | 飞书应用 App ID |
106
- | `FEISHU_APP_SECRET` | 飞书应用 App Secret |
107
-
108
- **OpenCode:**
143
+ ## 架构概览
109
144
 
110
- | 变量 | 类型 | 默认值 | 说明 |
111
- |------|------|--------|------|
112
- | `OPENCODE_TIMEOUT` | number | 120000 | 单次对话超时(毫秒) |
145
+ ```mermaid
146
+ flowchart LR
147
+ FeishuApp["飞书客户端"] -->|"WebSocket 长连接"| Gateway["gateway.ts\n(飞书网关)"]
148
+ Gateway --> Dedup["dedup.ts\n(消息去重)"]
149
+ Gateway --> GroupFilter["group-filter.ts\n(@提及检测)"]
150
+ Gateway -->|"bot.added 事件"| History["history.ts\n(历史摄入)"]
151
+ Gateway -->|"im.message.receive_v1"| Chat["chat.ts\n(对话处理)"]
152
+ Chat --> Session["session.ts\n(会话管理)"]
153
+ Chat -->|"prompt/messages"| OpenCodeAPI["OpenCode SDK"]
154
+ History -->|"noReply: true"| OpenCodeAPI
155
+ OpenCodeAPI --> EventHook["event 钩子\n(message.part.updated)"]
156
+ EventHook --> EventHandler["event.ts\n(事件处理)"]
157
+ EventHandler -->|"实时更新占位"| Sender["sender.ts\n(消息发送)"]
158
+ Chat -->|"最终回复"| Sender
159
+ Sender --> FeishuApp
160
+ ```
113
161
 
114
- 服务固定连接 `http://localhost:4096`,不可配置。
162
+ **主要流程说明:**
115
163
 
116
- **机器人行为:**
164
+ - **对话路径**:用户消息 → `gateway.ts` 接收 → `dedup.ts` 去重 → `group-filter.ts` 判断是否回复 → `chat.ts` 获取/创建 OpenCode 会话 → 发送 prompt → 轮询消息 → `sender.ts` 回写飞书
165
+ - **实时更新**:`chat.ts` 发送 prompt 后创建占位消息,OpenCode 通过 `event` 钩子推送 `message.part.updated` 事件,`event.ts` 处理后实时更新飞书占位消息内容
166
+ - **静默监听**:群聊中未被 @提及的消息通过 `noReply: true` 发送给 OpenCode,仅记录上下文但不触发 AI 回复
167
+ - **入群摄入**:`im.chat.member.bot.added_v1` 事件触发后,`history.ts` 拉取群聊最近 50 条历史消息并以 `noReply: true` 注入 OpenCode
117
168
 
118
- | 变量 | 类型 | 默认值 | 说明 |
119
- |------|------|--------|------|
120
- | `BOT_THINKING_DELAY` | number | 2500 | 发送「正在思考…」前的延迟(毫秒) |
121
- | `BOT_ENABLE_STREAMING` | boolean | true | 是否启用流式更新占位消息 |
122
- | `BOT_STREAM_INTERVAL` | number | 1000 | 流式更新间隔(毫秒) |
169
+ ---
123
170
 
124
- ### 5.2 配置文件
171
+ ## 配置说明
125
172
 
126
- 除环境变量外,可从以下路径读取 JSON 配置(先加载的为底,后加载的覆盖):
173
+ 配置文件路径:`~/.config/opencode/plugins/feishu.json`
127
174
 
128
- - `~/.config/opencode/feishu-bot.json`(或由 `XDG_CONFIG_HOME` 指定目录下的 `opencode/feishu-bot.json`)
129
- - 项目目录下的 `.opencode/feishu-bot.json`
175
+ | 字段 | 类型 | 必填 | 默认值 | 说明 |
176
+ |------|------|:----:|--------|------|
177
+ | `appId` | string | 是 | — | 飞书应用 App ID |
178
+ | `appSecret` | string | 是 | — | 飞书应用 App Secret |
179
+ | `timeout` | number | 否 | `120000` | 单次对话超时时间(毫秒) |
180
+ | `thinkingDelay` | number | 否 | `2500` | 发送「正在思考…」前的延迟(毫秒),设为 0 可禁用占位消息 |
181
+ | `proxy` | string | 否 | — | HTTP/HTTPS 代理地址(如 `http://127.0.0.1:7890`) |
130
182
 
131
- 结构与 `Config` 一致,例如:
183
+ 完整配置示例:
132
184
 
133
185
  ```json
134
186
  {
135
- "feishu": {
136
- "appId": "cli_xxxx",
137
- "appSecret": "xxxx"
138
- },
139
- "opencode": {
140
- "timeout": 120000
141
- },
142
- "bot": {
143
- "thinkingDelay": 2500,
144
- "enableStreaming": true,
145
- "streamInterval": 1000
146
- }
187
+ "appId": "cli_xxxxxxxxxxxx",
188
+ "appSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
189
+ "timeout": 120000,
190
+ "thinkingDelay": 2500
147
191
  }
148
192
  ```
149
193
 
150
- 环境变量会覆盖上述文件中同名字段。
151
-
152
194
  ---
153
195
 
154
- ## 安装与运行
196
+ ## 日志
155
197
 
156
- ```bash
157
- npm install
158
- npm run build
159
- npm start
160
- ```
198
+ ### Debug 日志文件
161
199
 
162
- 开发时可在项目根目录执行:
200
+ 插件每次初始化时会创建(并覆盖)以下文件:
163
201
 
164
- ```bash
165
- npm run dev
166
202
  ```
167
-
168
- 另开终端运行 `node dist/index.js`。启动成功后,日志中应依次出现:
169
-
170
- | 日志内容 | 含义 |
171
- |----------|------|
172
- | `配置加载成功` | 环境变量/配置文件已加载 |
173
- | `Bot open_id 获取成功` | 成功获取 bot 身份信息(用于 @提及检测) |
174
- | `OpenCode 连接状态 healthy: true` | OpenCode 服务可达 |
175
- | `OpenCode 事件流连接中…` | 正在连接 OpenCode SSE |
176
- | `Feishu WebSocket gateway started` | 飞书 WebSocket 网关已启动 |
177
- | `服务就绪:飞书网关已连接` | 可正常收发消息 |
178
-
179
- ---
180
-
181
- ## 本地打包与安装
182
-
183
- ### 打包为 tarball
184
-
185
- ```bash
186
- npm run build
187
- npm pack
203
+ ~/feishu-debug.log
188
204
  ```
189
205
 
190
- 生成 `opencode-feishu-0.1.0.tgz`,包含 `dist/` 和 `README.md`。
206
+ 该文件记录插件初始化过程中的详细信息,包含配置加载、bot open_id 获取、WebSocket 连接诊断等,排查问题时首先查看此文件。
191
207
 
192
- ### 全局安装
208
+ ### OpenCode 日志系统
193
209
 
194
- ```bash
195
- # 从 tarball 安装
196
- npm install -g ./opencode-feishu-0.1.0.tgz
197
-
198
- # 或开发模式(链接本地目录)
199
- npm link
200
- ```
210
+ 插件通过 `client.app.log()` 将结构化日志输出到 OpenCode 日志系统:
201
211
 
202
- 安装后可在任意目录运行 `opencode-feishu` 命令启动服务。
212
+ - **service**:`opencode-feishu`
213
+ - **level**:`info`、`warn`、`error`
214
+ - 可在 OpenCode 的日志界面中按 service 过滤查看
203
215
 
204
- ### 作为依赖安装到其他项目
216
+ ### Fallback 行为
205
217
 
206
- ```bash
207
- npm install /path/to/opencode-feishu-0.1.0.tgz
208
- ```
218
+ 若 `client.app.log()` 调用失败(如 OpenCode 版本不兼容),插件会降级到 `console.log`/`console.error` 输出,日志格式为 JSON 行。
209
219
 
210
220
  ---
211
221
 
212
222
  ## 对话流程说明
213
223
 
214
- - **会话获取/创建**:按飞书会话键(单聊 `feishu-p2p-<userId>`,群聊 `feishu-group-<chatId>`)先查内存缓存,再按 OpenCode 会话标题前缀匹配恢复,若无则新建会话。
215
- - **「正在思考…」**:发送用户消息后,延迟 `BOT_THINKING_DELAY`(默认 2500ms)再在飞书发送「正在思考…」占位消息;回复就绪后更新该条消息为最终内容,再删除占位。
216
- - **轮询**:每 1.5 秒拉取一次该 OpenCode 会话的消息列表,取最后一条 assistant 文本;若连续 2 次相同则视为稳定,结束轮询。
217
- - **SSE 流式更新**:同时订阅 OpenCode 的 SSE 事件流;收到 `message.part.updated` 时实时更新飞书占位内容。断线后 5 秒自动重连。
218
- - **超时**:若在 `OPENCODE_TIMEOUT`(默认 120 秒)内未得到稳定回复,将返回「响应超时」并结束等待。
224
+ - **会话获取/创建**:按飞书会话键(单聊 `feishu-p2p-<userId>`,群聊 `feishu-group-<chatId>`)查找已有 OpenCode 会话(按标题前缀匹配),若无则新建。
225
+ - **「正在思考…」**:发送用户消息后,延迟 `thinkingDelay`(默认 2500ms)再在飞书发送「正在思考…」占位消息;等待期间通过事件钩子实时更新内容,回复就绪后更新为最终内容。
226
+ - **轮询**:每 1500ms 拉取一次该 OpenCode 会话的消息列表,取最后一条 assistant 文本;若连续 2 次内容相同则视为稳定,结束轮询。
227
+ - **实时更新**:插件 `event` 钩子接收 `message.part.updated` 事件,实时将累积的回复内容更新到飞书占位消息。
228
+ - **超时**:若在 `timeout`(默认 120 秒)内未得到稳定回复,将返回「⚠️ 响应超时」并结束等待。
219
229
  - **纯中继**:所有消息(包括以 `/` 开头的文本)均原样转发给 OpenCode,由 OpenCode 决定模型、Agent 与行为。
220
230
 
221
231
  ---
222
232
 
223
233
  ## 群聊行为
224
234
 
225
- Bot 在群聊中始终以**静默监听模式**运行:
226
-
227
235
  ### 静默监听
228
236
 
229
- - 群聊中的**所有文本消息**都会转发给 OpenCode 作为对话上下文(使用 `noReply: true`,不触发 AI 回复、不消耗 AI tokens)。
237
+ - 群聊中的**所有文本消息**都会转发给 OpenCode 作为对话上下文(使用 `noReply: true`,不触发 AI 回复,不消耗 AI tokens)。
230
238
  - 仅在 bot 被**直接 @提及**时,才触发正常的 AI 对话并在飞书群内回复。
231
239
  - 未被 @提及的消息在飞书侧完全无感——不会产生任何回复或可见的 bot 行为。
232
240
 
@@ -243,11 +251,11 @@ Bot 在群聊中始终以**静默监听模式**运行:
243
251
  | 单聊(私聊) | 是 | 否 | 是 |
244
252
  | 群聊 + bot 被 @提及 | 是 | 否 | 是 |
245
253
  | 群聊 + bot 未被 @提及 | 是 | **是** | **否** |
246
- | bot 首次入群 | 历史消息 | **是** | **否** |
254
+ | bot 首次入群(历史摄入) | 历史消息 | **是** | **否** |
247
255
 
248
256
  ### 群聊发送者身份
249
257
 
250
- 发往 OpenCode 的群聊消息会带上发送者身份,便于 AI 区分是谁在说话:每条消息文本前会加上 `[open_id]` 前缀(飞书用户的 `open_id`),例如:
258
+ 发往 OpenCode 的群聊消息会带上发送者身份前缀,便于 AI 区分是谁在说话:
251
259
 
252
260
  ```
253
261
  [ou_xxxxxxxxxxxx]: 帮我看一下这个 bug
@@ -260,31 +268,52 @@ Bot 在群聊中始终以**静默监听模式**运行:
260
268
  ## 会话管理
261
269
 
262
270
  - **会话键**:单聊为 `feishu-p2p-<发送者 userId>`,群聊为 `feishu-group-<群 chatId>`。
263
- - **OpenCode 会话标题**:新建会话标题格式为 `Feishu-feishu-<chatType>-<id>-<时间戳>`,用于重启后按标题前缀恢复。
264
- - **缓存**:内存中会话键到 OpenCode 会话 ID 的映射保留 24 小时;超时可通过 `cleanupExpired()` 清理(当前实现中由会话管理器内部使用)。
265
- - **恢复**:进程重启后无内存缓存,会通过「列出 OpenCode 会话 + 按标题前缀匹配」恢复对应飞书会话的 OpenCode 会话,找不到则新建。
271
+ - **OpenCode 会话标题**:格式为 `Feishu-<sessionKey>-<时间戳>`,用于进程重启后按标题前缀恢复会话。
272
+ - **恢复**:进程重启后,通过「列出 OpenCode 会话 + 按标题前缀匹配」恢复对应飞书会话的 OpenCode 会话,找不到则新建。
273
+ - **注意**:若手动修改了 OpenCode 会话标题,重启后将无法恢复该会话,会自动新建一个。
266
274
 
267
275
  ---
268
276
 
269
- ## 部署建议
277
+ ## 开发指南
270
278
 
271
- - **进程常驻**:使用 systemd、pm2 或 Docker 等运行 `node dist/index.js`,并确保环境变量或配置文件路径正确。
272
- - **日志**:标准输出为 JSON 行日志,可按需重定向或接入日志系统。
273
- - **健康检查**:可直接请求 OpenCode 的 v2 健康检查接口(若需自建探活)。
279
+ ### 构建命令
274
280
 
275
- ---
281
+ ```bash
282
+ # 安装依赖
283
+ npm install
276
284
 
277
- ## 常见问题与排查
285
+ # 一次性构建(生成 dist/)
286
+ npm run build
278
287
 
279
- | 现象 | 可能原因 | 处理 |
280
- |------|----------|------|
281
- | 启动报错「Missing Feishu config」 | 未设置 `FEISHU_APP_ID` 或 `FEISHU_APP_SECRET` | 在 `.env` 或配置文件中填写飞书凭证 |
282
- | 日志中「OpenCode 连接状态 healthy: false」 | OpenCode 未启动或地址不可达 | 确认 OpenCode 已在本机 `http://localhost:4096` 运行 |
283
- | 日志中「Bot open_id 为空」或「fallback 模式」 | bot info API 调用失败 | 检查飞书 App ID/Secret 是否正确;fallback 模式下任何 @提及都会触发回复 |
284
- | 群聊中不回复 | 未 @提及 bot 或未使用长连接 | 在群中 @bot 后发送消息;在飞书开放平台将订阅方式改为「长连接」 |
285
- | 入群后未摄入历史 | 未订阅 `im.chat.member.bot.added_v1` 事件或缺少群消息读取权限 | 在飞书开放平台添加事件订阅并开通 `im:message:readonly` 权限 |
286
- | 回复显示「响应超时」 | 等待时间超过 `OPENCODE_TIMEOUT` 或 OpenCode 响应过慢 | 适当增大 `OPENCODE_TIMEOUT` 或检查 OpenCode 与模型状态 |
287
- | 同一条消息被处理多次 | 飞书 WebSocket 重复投递 | 服务内对同一 `messageId` 在 10 分钟内去重,一般无需处理;若仍异常可检查 `dedup` 逻辑 |
288
+ # 开发模式(监听文件变更并自动重新构建)
289
+ npm run dev
290
+
291
+ # 仅做类型检查,不生成文件
292
+ npm run typecheck
293
+ ```
294
+
295
+ ### 开发流程
296
+
297
+ 1. 修改 `src/` 下的源文件
298
+ 2. 运行 `npm run dev` 保持后台自动构建
299
+ 3. 重启 OpenCode 使插件重新加载
300
+ 4. 查看 `~/feishu-debug.log` 排查初始化问题
301
+ 5. 在 OpenCode 日志界面按 `service=opencode-feishu` 过滤查看运行日志
302
+
303
+ ### 添加新的事件处理器
304
+
305
+ 1. 在 `src/feishu/gateway.ts` 中注册新的飞书事件类型
306
+ 2. 在 `src/handler/` 中添加对应处理逻辑
307
+ 3. 在 `src/index.ts` 中连接处理器到网关回调
308
+
309
+ ### 调整轮询行为
310
+
311
+ 在 `src/handler/chat.ts` 中修改以下常量:
312
+
313
+ ```typescript
314
+ const POLL_INTERVAL_MS = 1500 // 轮询间隔
315
+ const STABLE_POLLS = 2 // 连续多少次相同内容视为稳定
316
+ ```
288
317
 
289
318
  ---
290
319
 
@@ -293,23 +322,19 @@ Bot 在群聊中始终以**静默监听模式**运行:
293
322
  ```
294
323
  opencode-feishu/
295
324
  ├── src/
296
- │ ├── index.ts # 入口:加载配置、获取 bot info、启动网关与事件流、挂接对话
297
- │ ├── config.ts # 多源配置加载(文件 + 环境变量)
298
- │ ├── types.ts # 配置与消息上下文等类型定义
325
+ │ ├── index.ts # 插件入口:导出 FeishuPlugin,初始化配置/网关
326
+ │ ├── types.ts # 类型定义(FeishuPluginConfig, ResolvedConfig, LogFn)
327
+ │ ├── types/ws.d.ts # WebSocket 类型声明
328
+ │ ├── session.ts # 会话管理(查找/创建 OpenCode 会话,标题前缀匹配)
299
329
  │ ├── feishu/
300
- │ │ ├── gateway.ts # 飞书 WebSocket 网关、消息回调与 bot 入群事件
301
- │ │ ├── sender.ts # 飞书消息发送、更新、删除
302
- │ │ ├── dedup.ts # 消息去重(10 分钟窗口)
303
- │ │ ├── group-filter.ts # 群聊 @提及检测(仅在 bot 被直接 @时回复)
304
- │ │ └── history.ts # 入群历史上下文摄入
305
- ├── handler/
306
- │ └── chat.ts # 对话:静默监听 / 占位、prompt、轮询、回写
307
- ├── opencode/
308
- │ │ ├── client.ts # OpenCode SDK 封装(会话、消息、健康、SSE、noReply)
309
- │ │ └── events.ts # OpenCode SSE 订阅与占位实时更新
310
- │ └── session/
311
- │ └── manager.ts # 飞书会话键与 OpenCode 会话的映射与恢复
312
- ├── .env.example
330
+ │ │ ├── gateway.ts # 飞书 WebSocket 网关、消息回调与 bot 入群事件
331
+ │ │ ├── sender.ts # 飞书消息发送、更新、删除
332
+ │ │ ├── dedup.ts # 消息去重(10 分钟窗口)
333
+ │ │ ├── group-filter.ts # 群聊 @提及检测(仅在 bot 被直接 @时回复)
334
+ │ │ └── history.ts # 入群历史上下文摄入
335
+ └── handler/
336
+ ├── chat.ts # 对话处理(prompt、轮询、回复)
337
+ └── event.ts # OpenCode 事件处理(message.part.updated 实时更新)
313
338
  ├── package.json
314
339
  ├── tsup.config.ts
315
340
  └── README.md
@@ -317,6 +342,22 @@ opencode-feishu/
317
342
 
318
343
  ---
319
344
 
345
+ ## 常见问题与排查
346
+
347
+ | 现象 | 可能原因 | 处理 |
348
+ |------|----------|------|
349
+ | 启动报错「缺少飞书配置文件」 | 未创建配置文件或路径不对 | 在 `~/.config/opencode/plugins/feishu.json` 创建配置文件 |
350
+ | 启动报错「飞书配置不完整」 | `appId` 或 `appSecret` 字段缺失 | 检查配置文件中是否包含这两个字段 |
351
+ | 日志中「Bot open_id 为空」或「fallback 模式」 | bot info API 调用失败 | 检查飞书 App ID/Secret 是否正确;fallback 模式下任何 @提及都会触发回复 |
352
+ | 群聊中不回复 | 未 @提及 bot,或未使用长连接订阅方式 | 在群中 @bot 后发送消息;确认飞书开放平台订阅方式为「长连接」 |
353
+ | 入群后未摄入历史 | 未订阅 `im.chat.member.bot.added_v1` 事件,或缺少群消息读取权限 | 在飞书开放平台添加事件订阅并开通 `im:message:readonly` 权限 |
354
+ | 回复显示「⚠️ 响应超时」 | 等待时间超过 `timeout`,或 OpenCode/模型响应过慢 | 适当增大配置文件中的 `timeout` 值,或检查 OpenCode 与模型状态 |
355
+ | 同一条消息被处理多次 | 飞书 WebSocket 重复投递 | 插件对同一 `messageId` 在 10 分钟内自动去重,一般无需处理;如仍异常可查看 `feishu-debug.log` |
356
+ | 看不到插件日志 | OpenCode 版本不支持 `client.app.log()` | 插件会降级到 console 输出,查看 OpenCode 进程的标准输出 |
357
+ | 插件未被 OpenCode 加载 | `opencode.json` 未声明插件,或插件目录路径错误 | 确认 `~/.config/opencode/opencode.json` 中 `"plugin"` 数组包含 `"opencode-feishu"` |
358
+
359
+ ---
360
+
320
361
  ## 许可证
321
362
 
322
363
  MIT