opencode-lark 0.1.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 (138) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/LICENSE +21 -0
  3. package/README.md +322 -0
  4. package/README.zh-CN.md +324 -0
  5. package/bin/opencode-lark.js +2 -0
  6. package/dist/channel/base-plugin.d.ts +31 -0
  7. package/dist/channel/base-plugin.d.ts.map +1 -0
  8. package/dist/channel/base-plugin.js +42 -0
  9. package/dist/channel/base-plugin.js.map +1 -0
  10. package/dist/channel/feishu/feishu-plugin.d.ts +36 -0
  11. package/dist/channel/feishu/feishu-plugin.d.ts.map +1 -0
  12. package/dist/channel/feishu/feishu-plugin.js +149 -0
  13. package/dist/channel/feishu/feishu-plugin.js.map +1 -0
  14. package/dist/channel/feishu/index.d.ts +2 -0
  15. package/dist/channel/feishu/index.d.ts.map +1 -0
  16. package/dist/channel/feishu/index.js +2 -0
  17. package/dist/channel/feishu/index.js.map +1 -0
  18. package/dist/channel/index.d.ts +4 -0
  19. package/dist/channel/index.d.ts.map +1 -0
  20. package/dist/channel/index.js +4 -0
  21. package/dist/channel/index.js.map +1 -0
  22. package/dist/channel/manager.d.ts +37 -0
  23. package/dist/channel/manager.d.ts.map +1 -0
  24. package/dist/channel/manager.js +68 -0
  25. package/dist/channel/manager.js.map +1 -0
  26. package/dist/channel/mock/mock-plugin.d.ts +24 -0
  27. package/dist/channel/mock/mock-plugin.d.ts.map +1 -0
  28. package/dist/channel/mock/mock-plugin.js +42 -0
  29. package/dist/channel/mock/mock-plugin.js.map +1 -0
  30. package/dist/channel/types.d.ts +226 -0
  31. package/dist/channel/types.d.ts.map +1 -0
  32. package/dist/channel/types.js +7 -0
  33. package/dist/channel/types.js.map +1 -0
  34. package/dist/cron/cron-service.d.ts +40 -0
  35. package/dist/cron/cron-service.d.ts.map +1 -0
  36. package/dist/cron/cron-service.js +140 -0
  37. package/dist/cron/cron-service.js.map +1 -0
  38. package/dist/cron/heartbeat.d.ts +30 -0
  39. package/dist/cron/heartbeat.d.ts.map +1 -0
  40. package/dist/cron/heartbeat.js +76 -0
  41. package/dist/cron/heartbeat.js.map +1 -0
  42. package/dist/feishu/api-client.d.ts +19 -0
  43. package/dist/feishu/api-client.d.ts.map +1 -0
  44. package/dist/feishu/api-client.js +98 -0
  45. package/dist/feishu/api-client.js.map +1 -0
  46. package/dist/feishu/card-builder.d.ts +10 -0
  47. package/dist/feishu/card-builder.d.ts.map +1 -0
  48. package/dist/feishu/card-builder.js +74 -0
  49. package/dist/feishu/card-builder.js.map +1 -0
  50. package/dist/feishu/cardkit-client.d.ts +48 -0
  51. package/dist/feishu/cardkit-client.d.ts.map +1 -0
  52. package/dist/feishu/cardkit-client.js +97 -0
  53. package/dist/feishu/cardkit-client.js.map +1 -0
  54. package/dist/feishu/message-dedup.d.ts +28 -0
  55. package/dist/feishu/message-dedup.d.ts.map +1 -0
  56. package/dist/feishu/message-dedup.js +58 -0
  57. package/dist/feishu/message-dedup.js.map +1 -0
  58. package/dist/feishu/webhook-server.d.ts +20 -0
  59. package/dist/feishu/webhook-server.d.ts.map +1 -0
  60. package/dist/feishu/webhook-server.js +111 -0
  61. package/dist/feishu/webhook-server.js.map +1 -0
  62. package/dist/feishu/ws-client.d.ts +17 -0
  63. package/dist/feishu/ws-client.d.ts.map +1 -0
  64. package/dist/feishu/ws-client.js +158 -0
  65. package/dist/feishu/ws-client.js.map +1 -0
  66. package/dist/handler/interactive-handler.d.ts +16 -0
  67. package/dist/handler/interactive-handler.d.ts.map +1 -0
  68. package/dist/handler/interactive-handler.js +86 -0
  69. package/dist/handler/interactive-handler.js.map +1 -0
  70. package/dist/handler/interactive-poller.d.ts +16 -0
  71. package/dist/handler/interactive-poller.d.ts.map +1 -0
  72. package/dist/handler/interactive-poller.js +147 -0
  73. package/dist/handler/interactive-poller.js.map +1 -0
  74. package/dist/handler/message-handler.d.ts +34 -0
  75. package/dist/handler/message-handler.d.ts.map +1 -0
  76. package/dist/handler/message-handler.js +305 -0
  77. package/dist/handler/message-handler.js.map +1 -0
  78. package/dist/handler/streaming-integration.d.ts +21 -0
  79. package/dist/handler/streaming-integration.d.ts.map +1 -0
  80. package/dist/handler/streaming-integration.js +325 -0
  81. package/dist/handler/streaming-integration.js.map +1 -0
  82. package/dist/index.d.ts +16 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +281 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/memory/memory-manager.d.ts +16 -0
  87. package/dist/memory/memory-manager.d.ts.map +1 -0
  88. package/dist/memory/memory-manager.js +58 -0
  89. package/dist/memory/memory-manager.js.map +1 -0
  90. package/dist/session/progress-tracker.d.ts +12 -0
  91. package/dist/session/progress-tracker.d.ts.map +1 -0
  92. package/dist/session/progress-tracker.js +46 -0
  93. package/dist/session/progress-tracker.js.map +1 -0
  94. package/dist/session/session-manager.d.ts +15 -0
  95. package/dist/session/session-manager.d.ts.map +1 -0
  96. package/dist/session/session-manager.js +91 -0
  97. package/dist/session/session-manager.js.map +1 -0
  98. package/dist/streaming/event-processor.d.ts +74 -0
  99. package/dist/streaming/event-processor.d.ts.map +1 -0
  100. package/dist/streaming/event-processor.js +240 -0
  101. package/dist/streaming/event-processor.js.map +1 -0
  102. package/dist/streaming/session-observer.d.ts +19 -0
  103. package/dist/streaming/session-observer.d.ts.map +1 -0
  104. package/dist/streaming/session-observer.js +140 -0
  105. package/dist/streaming/session-observer.js.map +1 -0
  106. package/dist/streaming/streaming-card.d.ts +37 -0
  107. package/dist/streaming/streaming-card.d.ts.map +1 -0
  108. package/dist/streaming/streaming-card.js +139 -0
  109. package/dist/streaming/streaming-card.js.map +1 -0
  110. package/dist/streaming/subagent-card.d.ts +32 -0
  111. package/dist/streaming/subagent-card.d.ts.map +1 -0
  112. package/dist/streaming/subagent-card.js +103 -0
  113. package/dist/streaming/subagent-card.js.map +1 -0
  114. package/dist/streaming/subagent-tracker.d.ts +45 -0
  115. package/dist/streaming/subagent-tracker.d.ts.map +1 -0
  116. package/dist/streaming/subagent-tracker.js +118 -0
  117. package/dist/streaming/subagent-tracker.js.map +1 -0
  118. package/dist/types.d.ts +55 -0
  119. package/dist/types.d.ts.map +1 -0
  120. package/dist/types.js +5 -0
  121. package/dist/types.js.map +1 -0
  122. package/dist/utils/config.d.ts +197 -0
  123. package/dist/utils/config.d.ts.map +1 -0
  124. package/dist/utils/config.js +87 -0
  125. package/dist/utils/config.js.map +1 -0
  126. package/dist/utils/db.d.ts +12 -0
  127. package/dist/utils/db.d.ts.map +1 -0
  128. package/dist/utils/db.js +35 -0
  129. package/dist/utils/db.js.map +1 -0
  130. package/dist/utils/event-listeners.d.ts +12 -0
  131. package/dist/utils/event-listeners.d.ts.map +1 -0
  132. package/dist/utils/event-listeners.js +21 -0
  133. package/dist/utils/event-listeners.js.map +1 -0
  134. package/dist/utils/logger.d.ts +11 -0
  135. package/dist/utils/logger.d.ts.map +1 -0
  136. package/dist/utils/logger.js +38 -0
  137. package/dist/utils/logger.js.map +1 -0
  138. package/package.json +41 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+
7
+ ## [0.1.1] - 2026-02-26
8
+
9
+ ### Added
10
+
11
+ - Interactive question cards: when the AI agent asks a question, Feishu users see a card with clickable answer buttons
12
+ - Interactive permission cards: file edit, bash, and webfetch approvals rendered as Feishu cards with Allow/Reject buttons
13
+ - Card action callbacks via WebSocket long connection (`card.action.trigger`)
14
+ - Toast feedback + card replacement on button click (buttons disabled after answering)
15
+ - Interactive poller fallback: polls `/question` and `/permission` endpoints every 3s in case SSE events are missed
16
+ - Chinese README (`README.zh-CN.md`)
17
+
18
+ ### Fixed
19
+
20
+ - Card action callback timeout (error 200340): handler now returns immediately within Feishu's 3s requirement
21
+ - Interactive cards sent as direct JSON instead of CardKit v2 wrapper (fixes `content type illegal` error)
22
+ - Permission event type corrected from `permission.updated` to `permission.asked`
23
+ - POST timeout no longer kills SSE listener when session is blocked on a question
24
+
25
+ ### Changed
26
+
27
+ - Removed dead code: `editMessage`, `appendText`, `SessionBusy`, `ReasoningDelta`
28
+
29
+ ### Docs
30
+
31
+ - Added callback subscription setup guide (Step 8) — required for interactive cards
32
+ - Added `cardkit:card:write` permission to required permissions table
33
+ - Added error 200340 to troubleshooting table
34
+
35
+ ## [0.1.0] - 2026-02-25
36
+
37
+ ### Initial Open-Source Release
38
+
39
+ - Feishu/Lark ↔ opencode bidirectional messaging via WebSocket long connection
40
+ - Real-time streaming cards with tool progress indicators (CardKit v2)
41
+ - Sub-agent task tracking with expandable progress cards
42
+ - Channel abstraction layer (`ChannelPlugin` interface for extensibility)
43
+ - Session management with TUI session discovery and automatic binding
44
+ - Cron scheduling service with configurable jobs
45
+ - Heartbeat monitoring with Feishu status notifications
46
+ - SQLite-backed conversation memory with context injection
47
+ - Message deduplication to prevent duplicate processing
48
+ - Configurable via JSONC config file + environment variables
49
+ - TypeScript with strict mode, zero-error build
50
+ - 248 unit + integration tests (vitest)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 opencode-feishu contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,322 @@
1
+ [中文版](README.zh-CN.md)
2
+
3
+ # opencode-lark
4
+
5
+ > Bridge Feishu group chats to opencode TUI sessions with real-time two-way messaging.
6
+
7
+ ![CI](https://github.com/guazi04/opencode-lark/actions/workflows/ci.yml/badge.svg)
8
+ ![npm](https://img.shields.io/npm/v/opencode-lark.svg)
9
+ ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
10
+
11
+ ---
12
+
13
+ ## Features
14
+
15
+ - **Real-time bridging** — Messages sent in Feishu arrive in your opencode TUI instantly, and agent replies stream back as live-updating cards.
16
+ - **WebSocket connection** — Uses Feishu's long-lived WebSocket mode. No webhook polling, no public IP required.
17
+ - **SSE streaming** — Consumes the opencode SSE event stream and debounces card updates to stay within rate limits.
18
+ - **Conversation memory** — SQLite-backed per-thread history is prepended to each message, giving the agent context across turns.
19
+ - **Session auto-discovery** — Finds and binds to the latest opencode TUI session for a working directory. Survives restarts.
20
+ - **Graceful recovery** — Reconnects to the opencode server with exponential backoff (up to 10 attempts) on startup.
21
+ - **Extensible channel layer** — `ChannelPlugin` interface lets you add Slack, Discord, or any other platform without touching core logic.
22
+
23
+ ---
24
+
25
+ ## Architecture
26
+
27
+ ```
28
+ Feishu client
29
+ ↕ WebSocket
30
+ Feishu Open Platform
31
+ ↕ WebSocket
32
+ opencode-lark (this project)
33
+ ↕ HTTP API + SSE
34
+ opencode server (localhost:4096)
35
+ ↕ stdin/stdout
36
+ opencode TUI
37
+ ```
38
+
39
+ > `opencode serve` runs the HTTP server. Use `opencode attach` in a separate terminal to view the session in TUI.
40
+
41
+ **Inbound (Feishu → TUI):** Feishu sends a message over WebSocket. opencode-lark normalizes it, resolves the bound session, prepends conversation history, then POSTs to the opencode API. The TUI sees the message immediately.
42
+
43
+ **Outbound (TUI → Feishu):** opencode-lark subscribes to the opencode SSE stream. As the agent produces text, `TextDelta` events accumulate and a debounced card update fires. Once `SessionIdle` arrives, the final card is flushed to Feishu.
44
+
45
+ ---
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ # Global install
51
+ npm install -g opencode-lark
52
+ # or
53
+ bun add -g opencode-lark
54
+ ```
55
+
56
+ Or clone and run from source:
57
+
58
+ ```bash
59
+ git clone https://github.com/guazi04/opencode-lark.git
60
+ cd opencode-lark
61
+ bun install
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Feishu App Setup
67
+
68
+ ### 1. Create an Internal App
69
+
70
+ 1. Open [Feishu Open Platform](https://open.feishu.cn/app)
71
+ 2. Click **Create App** → **Create Internal App**
72
+ 3. Fill in app name and description, then confirm
73
+
74
+ ### 2. Enable Bot Capability
75
+
76
+ Navigate to **App Features → Bot** and enable the bot capability.
77
+
78
+ ### 3. Get Credentials
79
+
80
+ Navigate to **Credentials & Basic Info** to find:
81
+
82
+ - **App ID** → set as `FEISHU_APP_ID`
83
+ - **App Secret** → set as `FEISHU_APP_SECRET`
84
+
85
+ You'll need these in Step 6 to configure opencode-lark.
86
+
87
+ ### 4. Configure Permissions
88
+
89
+ Navigate to **Development Config → Permissions & Scopes** and add the following:
90
+
91
+ | Permission | Scope Identifier | Purpose | Required |
92
+ |---|---|---|---|
93
+ | 获取与发送单聊、群组消息 | `im:message` | Send messages & update cards | ✅ |
94
+ | 获取用户发给机器人的单聊消息 | `im:message.p2p_msg:readonly` | Receive direct messages | ✅ |
95
+ | 获取群组中所有消息 | `im:message.group_msg` | Receive all group messages | ✅ |
96
+ | 获取群组中 @机器人的消息 | `im:message.group_at_msg:readonly` | Receive group messages that @mention the bot | ✅ |
97
+ | 获取与上传图片或文件资源 | `im:resource` | Handle message attachments | ✅ |
98
+ | 创建并发布卡片 | `cardkit:card:write` | Render interactive cards (questions, permissions) | ✅ |
99
+
100
+ ### 5. Publish the App
101
+
102
+ Navigate to **App Release → Version Management & Release**, create a version and submit for review. After approval, add the bot to your workspace.
103
+
104
+ > **Note**: Internal apps in trial status can be used by app administrators immediately without review for testing.
105
+
106
+ ### 6. Configure & Start opencode-lark
107
+
108
+ Before configuring event subscriptions, start opencode-lark so Feishu can detect the WebSocket connection.
109
+
110
+ 1. Clone the repo and install dependencies:
111
+ ```bash
112
+ git clone https://github.com/guazi04/opencode-lark.git
113
+ cd opencode-lark
114
+ bun install
115
+ ```
116
+
117
+ 2. Configure credentials:
118
+ ```bash
119
+ cp .env.example .env
120
+ ```
121
+ Open `.env` and fill in the `FEISHU_APP_ID` and `FEISHU_APP_SECRET` from Step 3.
122
+
123
+ 3. Start opencode server in one terminal:
124
+ ```bash
125
+ OPENCODE_SERVER_PORT=4096 opencode serve
126
+ ```
127
+
128
+ 4. Start opencode-lark in another terminal:
129
+ ```bash
130
+ bun run dev
131
+ ```
132
+ Keep this running while you configure event subscriptions in the next step.
133
+
134
+ > **Tip**: To see messages in real-time in the TUI, open a third terminal and attach to the session:
135
+ > ```bash
136
+ > opencode attach http://127.0.0.1:4096 --session {session_id}
137
+ > ```
138
+ > The `session_id` is shown in opencode-lark's startup logs (e.g. `Bound to TUI session: ... → ses_xxxxx`).
139
+
140
+ ### 7. Subscribe to Events
141
+
142
+ Navigate to **Development Config → Event Subscriptions** and:
143
+
144
+ 1. Select **Long Connection** (WebSocket) mode — no public IP required
145
+ 2. Add the following event:
146
+
147
+ | Event Name | Event Identifier | Purpose | Required |
148
+ |---|---|---|---|
149
+ | 接收消息 | `im.message.receive_v1` | Receive all user messages | ✅ |
150
+
151
+ > ⚠️ **Important**: opencode-lark must be running (Step 6) before you can save Long Connection mode. If you see "应用未建立长连接", go back to Step 6 and ensure the app is running.
152
+
153
+ ### 8. Subscribe to Callbacks (Interactive Cards)
154
+
155
+ Navigate to **Development Config → Event Subscriptions → Callback Subscription** (回调订阅) — this is a **separate section** from Event Subscription above.
156
+
157
+ 1. Select **Long Connection** (WebSocket) mode
158
+ 2. Add the following callback:
159
+
160
+ | Callback Name | Callback Identifier | Purpose | Required |
161
+ |---|---|---|---|
162
+ | 卡片回传交互 | `card.action.trigger` | Receive card button clicks (question answers, permission replies) | ✅ |
163
+
164
+ > ⚠️ **Important**: This is required for interactive cards (questions & permissions). Without it, clicking card buttons shows error `200340`.
165
+ >
166
+ > Event Subscription and Callback Subscription are **two separate settings**. You must configure both.
167
+
168
+ ### Troubleshooting
169
+
170
+ | Symptom | Likely Cause | Fix |
171
+ |---|---|---|
172
+ | Bot doesn't receive messages | WebSocket not enabled or wrong subscription | Check event subscription, ensure Long Connection mode is selected |
173
+ | "Invalid App ID or Secret" | Wrong credentials in .env | Double-check App ID and App Secret from Step 3 |
174
+ | Messages received but no reply | opencode server not running | Ensure opencode server is running: `OPENCODE_SERVER_PORT=4096 opencode serve` |
175
+ | Card not updating in real-time | Rate limit or debounce delay | Normal behavior — updates are debounced to stay within Feishu rate limits |
176
+ | Error `200340` when clicking card buttons | Callback subscription not configured | Go to **Callback Subscription** (回调订阅) → select Long Connection → add `card.action.trigger` |
177
+ | "应用未建立长连接" when saving Long Connection mode | App not running — Feishu requires an active WebSocket connection before saving | Start opencode-lark first (Step 6), then save the setting in Feishu console |
178
+ ---
179
+
180
+ ## Quick Start
181
+
182
+ If you've completed [Feishu App Setup](#feishu-app-setup) above, opencode-lark is already running. Skip to **Send a test message** below.
183
+
184
+ Otherwise:
185
+
186
+ ### Prerequisites
187
+
188
+ - **[Bun](https://bun.sh)** (required runtime — this project uses `bun:sqlite` which is Bun-only)
189
+ - **[opencode](https://opencode.ai)** installed locally
190
+ - A **Feishu Open Platform app** with credentials and event subscriptions configured (see [Feishu App Setup](#feishu-app-setup))
191
+
192
+ ### Steps
193
+
194
+ **1. Clone, install, and configure**
195
+
196
+ ```bash
197
+ git clone https://github.com/guazi04/opencode-lark.git
198
+ cd opencode-lark
199
+ bun install
200
+ cp .env.example .env
201
+ ```
202
+
203
+ Open `.env` and fill in `FEISHU_APP_ID` and `FEISHU_APP_SECRET`.
204
+
205
+ **2. Start opencode server**
206
+
207
+ ```bash
208
+ OPENCODE_SERVER_PORT=4096 opencode serve
209
+ ```
210
+
211
+ The opencode server listens on port 4096 by default (increments if that port is taken).
212
+
213
+ **3. Start opencode-lark**
214
+
215
+ In a second terminal:
216
+
217
+ ```bash
218
+ bun run dev
219
+ ```
220
+
221
+ Or from a global install:
222
+
223
+ ```bash
224
+ opencode-lark
225
+ ```
226
+
227
+ `dev` mode runs with `--watch`, so code changes trigger an automatic restart.
228
+
229
+ **4. Send a test message**
230
+
231
+ Send any message to your Feishu bot. On first contact it auto-discovers the latest TUI session and replies:
232
+
233
+ > Connected to session: ses_xxxxx
234
+
235
+ After that, Feishu and the TUI share a live two-way channel. To attach the TUI, see the tip in [Step 6](#6-configure--start-opencode-lark).
236
+
237
+ ---
238
+
239
+ ## Configuration
240
+
241
+ ### Environment Variables
242
+
243
+ | Variable | Required | Default | Description |
244
+ |----------|----------|---------|-------------|
245
+ | `FEISHU_APP_ID` | yes | | Feishu App ID |
246
+ | `FEISHU_APP_SECRET` | yes | | Feishu App Secret |
247
+ | `OPENCODE_SERVER_URL` | no | `http://localhost:4096` | opencode server URL |
248
+ | `FEISHU_WEBHOOK_PORT` | no | `3001` | Card action callback port |
249
+ | `OPENCODE_CWD` | no | `process.cwd()` | Override session discovery directory |
250
+ | `FEISHU_VERIFICATION_TOKEN` | no | | Event subscription verification token |
251
+ | `FEISHU_ENCRYPT_KEY` | no | | Event encryption key |
252
+
253
+ ### JSONC Config
254
+
255
+ `opencode-lark.jsonc` (gitignored; copy from `opencode-lark.example.jsonc`):
256
+ (also supports `opencode-feishu.jsonc` for backward compatibility)
257
+
258
+ ```jsonc
259
+ // opencode-lark.jsonc
260
+ {
261
+ "feishu": {
262
+ "appId": "${FEISHU_APP_ID}",
263
+ "appSecret": "${FEISHU_APP_SECRET}",
264
+ "verificationToken": "${FEISHU_VERIFICATION_TOKEN}",
265
+ "webhookPort": 3001,
266
+ "encryptKey": "${FEISHU_ENCRYPT_KEY}"
267
+ },
268
+ // Default opencode agent name. This should match an agent configured in your opencode setup.
269
+ // Common values: "build", "claude", "code" — check your opencode config for available agents.
270
+ "defaultAgent": "build",
271
+ "dataDir": "./data",
272
+ "progress": {
273
+ "debounceMs": 500,
274
+ "maxDebounceMs": 3000
275
+ }
276
+ }
277
+ ```
278
+
279
+ Supports `${ENV_VAR}` interpolation and JSONC comments. If no config file is found, the app builds a default config from `.env` values directly.
280
+
281
+ ---
282
+
283
+ ## Project Structure
284
+
285
+ ```
286
+ src/
287
+ ├── index.ts # Entry point, 9-phase startup + graceful shutdown
288
+ ├── types.ts # Shared type definitions
289
+ ├── channel/ # ChannelPlugin interface, ChannelManager, FeishuPlugin
290
+ ├── feishu/ # Feishu REST client, CardKit, WebSocket, message dedup
291
+ ├── handler/ # MessageHandler (inbound pipeline) + StreamingBridge (SSE → cards)
292
+ ├── session/ # TUI session discovery, thread→session mapping, progress cards
293
+ ├── streaming/ # EventProcessor (SSE parsing), SessionObserver, SubAgentTracker
294
+ ├── memory/ # SQLite-backed per-thread conversation history
295
+ ├── cron/ # CronService (scheduled jobs) + HeartbeatService
296
+ └── utils/ # Config loader, logger, SQLite init, EventListenerMap
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Development
302
+
303
+ ```bash
304
+ bun run dev # Watch mode, auto-restart on changes
305
+ bun run start # Production mode
306
+ bun run test:run # Run all tests (vitest)
307
+ bun run build # Compile TypeScript to dist/
308
+ ```
309
+
310
+ > **Note:** Use `bun run test:run` rather than `bun test`. The latter picks up both `src/` and `dist/` test files; `vitest` is configured to scope to `src/` only.
311
+
312
+ ---
313
+
314
+ ## Contributing
315
+
316
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on issues, pull requests, and code style.
317
+
318
+ ---
319
+
320
+ ## License
321
+
322
+ [MIT](LICENSE) © 2026 opencode-lark contributors