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.
- package/CHANGELOG.md +50 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/README.zh-CN.md +324 -0
- package/bin/opencode-lark.js +2 -0
- package/dist/channel/base-plugin.d.ts +31 -0
- package/dist/channel/base-plugin.d.ts.map +1 -0
- package/dist/channel/base-plugin.js +42 -0
- package/dist/channel/base-plugin.js.map +1 -0
- package/dist/channel/feishu/feishu-plugin.d.ts +36 -0
- package/dist/channel/feishu/feishu-plugin.d.ts.map +1 -0
- package/dist/channel/feishu/feishu-plugin.js +149 -0
- package/dist/channel/feishu/feishu-plugin.js.map +1 -0
- package/dist/channel/feishu/index.d.ts +2 -0
- package/dist/channel/feishu/index.d.ts.map +1 -0
- package/dist/channel/feishu/index.js +2 -0
- package/dist/channel/feishu/index.js.map +1 -0
- package/dist/channel/index.d.ts +4 -0
- package/dist/channel/index.d.ts.map +1 -0
- package/dist/channel/index.js +4 -0
- package/dist/channel/index.js.map +1 -0
- package/dist/channel/manager.d.ts +37 -0
- package/dist/channel/manager.d.ts.map +1 -0
- package/dist/channel/manager.js +68 -0
- package/dist/channel/manager.js.map +1 -0
- package/dist/channel/mock/mock-plugin.d.ts +24 -0
- package/dist/channel/mock/mock-plugin.d.ts.map +1 -0
- package/dist/channel/mock/mock-plugin.js +42 -0
- package/dist/channel/mock/mock-plugin.js.map +1 -0
- package/dist/channel/types.d.ts +226 -0
- package/dist/channel/types.d.ts.map +1 -0
- package/dist/channel/types.js +7 -0
- package/dist/channel/types.js.map +1 -0
- package/dist/cron/cron-service.d.ts +40 -0
- package/dist/cron/cron-service.d.ts.map +1 -0
- package/dist/cron/cron-service.js +140 -0
- package/dist/cron/cron-service.js.map +1 -0
- package/dist/cron/heartbeat.d.ts +30 -0
- package/dist/cron/heartbeat.d.ts.map +1 -0
- package/dist/cron/heartbeat.js +76 -0
- package/dist/cron/heartbeat.js.map +1 -0
- package/dist/feishu/api-client.d.ts +19 -0
- package/dist/feishu/api-client.d.ts.map +1 -0
- package/dist/feishu/api-client.js +98 -0
- package/dist/feishu/api-client.js.map +1 -0
- package/dist/feishu/card-builder.d.ts +10 -0
- package/dist/feishu/card-builder.d.ts.map +1 -0
- package/dist/feishu/card-builder.js +74 -0
- package/dist/feishu/card-builder.js.map +1 -0
- package/dist/feishu/cardkit-client.d.ts +48 -0
- package/dist/feishu/cardkit-client.d.ts.map +1 -0
- package/dist/feishu/cardkit-client.js +97 -0
- package/dist/feishu/cardkit-client.js.map +1 -0
- package/dist/feishu/message-dedup.d.ts +28 -0
- package/dist/feishu/message-dedup.d.ts.map +1 -0
- package/dist/feishu/message-dedup.js +58 -0
- package/dist/feishu/message-dedup.js.map +1 -0
- package/dist/feishu/webhook-server.d.ts +20 -0
- package/dist/feishu/webhook-server.d.ts.map +1 -0
- package/dist/feishu/webhook-server.js +111 -0
- package/dist/feishu/webhook-server.js.map +1 -0
- package/dist/feishu/ws-client.d.ts +17 -0
- package/dist/feishu/ws-client.d.ts.map +1 -0
- package/dist/feishu/ws-client.js +158 -0
- package/dist/feishu/ws-client.js.map +1 -0
- package/dist/handler/interactive-handler.d.ts +16 -0
- package/dist/handler/interactive-handler.d.ts.map +1 -0
- package/dist/handler/interactive-handler.js +86 -0
- package/dist/handler/interactive-handler.js.map +1 -0
- package/dist/handler/interactive-poller.d.ts +16 -0
- package/dist/handler/interactive-poller.d.ts.map +1 -0
- package/dist/handler/interactive-poller.js +147 -0
- package/dist/handler/interactive-poller.js.map +1 -0
- package/dist/handler/message-handler.d.ts +34 -0
- package/dist/handler/message-handler.d.ts.map +1 -0
- package/dist/handler/message-handler.js +305 -0
- package/dist/handler/message-handler.js.map +1 -0
- package/dist/handler/streaming-integration.d.ts +21 -0
- package/dist/handler/streaming-integration.d.ts.map +1 -0
- package/dist/handler/streaming-integration.js +325 -0
- package/dist/handler/streaming-integration.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +281 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/memory-manager.d.ts +16 -0
- package/dist/memory/memory-manager.d.ts.map +1 -0
- package/dist/memory/memory-manager.js +58 -0
- package/dist/memory/memory-manager.js.map +1 -0
- package/dist/session/progress-tracker.d.ts +12 -0
- package/dist/session/progress-tracker.d.ts.map +1 -0
- package/dist/session/progress-tracker.js +46 -0
- package/dist/session/progress-tracker.js.map +1 -0
- package/dist/session/session-manager.d.ts +15 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +91 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/streaming/event-processor.d.ts +74 -0
- package/dist/streaming/event-processor.d.ts.map +1 -0
- package/dist/streaming/event-processor.js +240 -0
- package/dist/streaming/event-processor.js.map +1 -0
- package/dist/streaming/session-observer.d.ts +19 -0
- package/dist/streaming/session-observer.d.ts.map +1 -0
- package/dist/streaming/session-observer.js +140 -0
- package/dist/streaming/session-observer.js.map +1 -0
- package/dist/streaming/streaming-card.d.ts +37 -0
- package/dist/streaming/streaming-card.d.ts.map +1 -0
- package/dist/streaming/streaming-card.js +139 -0
- package/dist/streaming/streaming-card.js.map +1 -0
- package/dist/streaming/subagent-card.d.ts +32 -0
- package/dist/streaming/subagent-card.d.ts.map +1 -0
- package/dist/streaming/subagent-card.js +103 -0
- package/dist/streaming/subagent-card.js.map +1 -0
- package/dist/streaming/subagent-tracker.d.ts +45 -0
- package/dist/streaming/subagent-tracker.d.ts.map +1 -0
- package/dist/streaming/subagent-tracker.js +118 -0
- package/dist/streaming/subagent-tracker.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/config.d.ts +197 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +87 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/db.d.ts +12 -0
- package/dist/utils/db.d.ts.map +1 -0
- package/dist/utils/db.js +35 -0
- package/dist/utils/db.js.map +1 -0
- package/dist/utils/event-listeners.d.ts +12 -0
- package/dist/utils/event-listeners.d.ts.map +1 -0
- package/dist/utils/event-listeners.js +21 -0
- package/dist/utils/event-listeners.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +38 -0
- package/dist/utils/logger.js.map +1 -0
- 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
|
+

|
|
8
|
+

|
|
9
|
+

|
|
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
|