@m1heng-clawd/feishu 0.1.7 → 0.1.8
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 +180 -8
- package/package.json +2 -2
- package/src/accounts.ts +100 -13
- package/src/bot.ts +123 -27
- package/src/channel.ts +163 -53
- package/src/client.ts +83 -35
- package/src/config-schema.ts +73 -1
- package/src/directory.ts +15 -9
- package/src/docx.ts +16 -6
- package/src/drive.ts +12 -6
- package/src/dynamic-agent.ts +131 -0
- package/src/media.ts +43 -35
- package/src/monitor.ts +215 -70
- package/src/outbound.ts +7 -7
- package/src/perm.ts +12 -6
- package/src/probe.ts +6 -9
- package/src/reactions.ts +19 -16
- package/src/reply-dispatcher.ts +19 -11
- package/src/send.ts +49 -43
- package/src/types.ts +16 -2
- package/src/typing.ts +11 -9
- package/src/wiki.ts +12 -6
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Feishu/Lark (飞书) channel plugin for [OpenClaw](https://github.com/openclaw/openclaw).
|
|
4
4
|
|
|
5
|
+
> **中文社区资料** - 配置教程、常见问题、使用技巧:[Wiki](https://github.com/m1heng/clawdbot-feishu/wiki)
|
|
6
|
+
|
|
5
7
|
[English](#english) | [中文](#中文)
|
|
6
8
|
|
|
7
9
|
---
|
|
@@ -113,7 +115,9 @@ The `feishu_bitable` tools support both URL formats:
|
|
|
113
115
|
|
|
114
116
|
In the Feishu Open Platform console, go to **Events & Callbacks**:
|
|
115
117
|
|
|
116
|
-
1. **Event configuration**: Select
|
|
118
|
+
1. **Event configuration**: Select the subscription mode matching your `connectionMode`:
|
|
119
|
+
- **Long connection** — for `connectionMode: "websocket"` (recommended, no public URL needed)
|
|
120
|
+
- **Request URL** — for `connectionMode: "webhook"` (requires a publicly accessible URL)
|
|
117
121
|
2. **Add event subscriptions**:
|
|
118
122
|
|
|
119
123
|
| Event | Description |
|
|
@@ -139,8 +143,8 @@ channels:
|
|
|
139
143
|
enabled: true
|
|
140
144
|
appId: "cli_xxxxx"
|
|
141
145
|
appSecret: "secret"
|
|
142
|
-
# Domain: "feishu" (China)
|
|
143
|
-
domain: "feishu"
|
|
146
|
+
# Domain: "feishu" (China), "lark" (International), or custom URL
|
|
147
|
+
domain: "feishu" # or "https://open.xxx.cn" for private deployment
|
|
144
148
|
# Connection mode: "websocket" (recommended) or "webhook"
|
|
145
149
|
connectionMode: "websocket"
|
|
146
150
|
# DM policy: "pairing" | "open" | "allowlist"
|
|
@@ -155,6 +159,45 @@ channels:
|
|
|
155
159
|
renderMode: "auto"
|
|
156
160
|
```
|
|
157
161
|
|
|
162
|
+
#### Connection Mode
|
|
163
|
+
|
|
164
|
+
Two connection modes are available for receiving events from Feishu:
|
|
165
|
+
|
|
166
|
+
| Mode | Description |
|
|
167
|
+
|------|-------------|
|
|
168
|
+
| `websocket` | (Default, recommended) Long-polling WebSocket connection. No public URL required, works behind NAT/firewall. Best for local development and most deployments. |
|
|
169
|
+
| `webhook` | HTTP server that receives event callbacks. Requires a publicly accessible URL. Suitable for server deployments behind a reverse proxy (e.g. Nginx). |
|
|
170
|
+
|
|
171
|
+
**WebSocket mode** (default, no extra config needed):
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
channels:
|
|
175
|
+
feishu:
|
|
176
|
+
connectionMode: "websocket" # or just omit this line
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
In Feishu console: Events & Callbacks → select **Long connection**.
|
|
180
|
+
|
|
181
|
+
**Webhook mode**:
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
channels:
|
|
185
|
+
feishu:
|
|
186
|
+
connectionMode: "webhook"
|
|
187
|
+
webhookPort: 3000 # HTTP server port (default: 3000)
|
|
188
|
+
webhookPath: "/feishu/events" # Event callback path (default: "/feishu/events")
|
|
189
|
+
encryptKey: "your_encrypt_key" # From Feishu console → Events & Callbacks → Encrypt Key
|
|
190
|
+
verificationToken: "your_verify_token" # From Feishu console → Events & Callbacks → Verification Token
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
In Feishu console: Events & Callbacks → select **Request URL** → set the URL to:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
https://your-domain.com/feishu/events
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
> **Note:** The Request URL must be HTTPS and publicly accessible. For local development, you can use tools like [ngrok](https://ngrok.com) to create a tunnel: `ngrok http 3000`, then use the generated URL.
|
|
200
|
+
|
|
158
201
|
#### Render Mode
|
|
159
202
|
|
|
160
203
|
| Mode | Description |
|
|
@@ -163,6 +206,46 @@ channels:
|
|
|
163
206
|
| `raw` | Always send replies as plain text. Markdown tables are converted to ASCII. |
|
|
164
207
|
| `card` | Always send replies as interactive cards with full markdown rendering (syntax highlighting, tables, clickable links). |
|
|
165
208
|
|
|
209
|
+
#### Dynamic Agent Creation (Multi-User Workspace Isolation)
|
|
210
|
+
|
|
211
|
+
When enabled, each DM user automatically gets their own isolated agent instance with a dedicated workspace. This provides complete isolation including separate conversation history, memory (MEMORY.md), and workspace files.
|
|
212
|
+
|
|
213
|
+
```yaml
|
|
214
|
+
channels:
|
|
215
|
+
feishu:
|
|
216
|
+
dmPolicy: "open"
|
|
217
|
+
allowFrom: ["*"]
|
|
218
|
+
dynamicAgentCreation:
|
|
219
|
+
enabled: true
|
|
220
|
+
# Template for workspace directory ({userId} = OpenID, {agentId} = generated agent ID)
|
|
221
|
+
workspaceTemplate: "~/workspaces/feishu-{agentId}"
|
|
222
|
+
# Template for agent config directory
|
|
223
|
+
agentDirTemplate: "~/.openclaw/agents/{agentId}/agent"
|
|
224
|
+
# Optional: limit total number of dynamic agents
|
|
225
|
+
maxAgents: 100
|
|
226
|
+
|
|
227
|
+
session:
|
|
228
|
+
# Also set dmScope for session isolation (conversation history)
|
|
229
|
+
dmScope: "per-peer"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
| Option | Description |
|
|
233
|
+
|--------|-------------|
|
|
234
|
+
| `enabled` | Enable dynamic agent creation for DM users |
|
|
235
|
+
| `workspaceTemplate` | Template for workspace path. Supports `{userId}` (OpenID) and `{agentId}` (= `feishu-{openId}`) |
|
|
236
|
+
| `agentDirTemplate` | Template for agent directory path |
|
|
237
|
+
| `maxAgents` | Optional limit on number of dynamic agents |
|
|
238
|
+
|
|
239
|
+
**How it works:**
|
|
240
|
+
1. When a new user sends a DM, the system creates a new agent entry in `openclaw.json`
|
|
241
|
+
2. A binding is created to route that user's DM to their dedicated agent
|
|
242
|
+
3. Workspace and agent directories are created automatically
|
|
243
|
+
4. Subsequent messages from that user go to their isolated agent
|
|
244
|
+
|
|
245
|
+
**Difference from `dmScope: "per-peer"`:**
|
|
246
|
+
- `dmScope: "per-peer"` only isolates conversation history
|
|
247
|
+
- `dynamicAgentCreation` provides full isolation (workspace, memory, identity, tools)
|
|
248
|
+
|
|
166
249
|
### Features
|
|
167
250
|
|
|
168
251
|
- WebSocket and Webhook connection modes
|
|
@@ -180,6 +263,7 @@ channels:
|
|
|
180
263
|
- **Bitable tools**: Read/write bitable (多维表格) records, supports both `/base/` and `/wiki/` URLs
|
|
181
264
|
- **@mention forwarding**: When you @mention someone in your message, the bot's reply will automatically @mention them too
|
|
182
265
|
- **Permission error notification**: When the bot encounters a Feishu API permission error, it automatically notifies the user with the permission grant URL
|
|
266
|
+
- **Dynamic agent creation**: Each DM user can have their own isolated agent instance with dedicated workspace (optional)
|
|
183
267
|
|
|
184
268
|
#### @Mention Forwarding
|
|
185
269
|
|
|
@@ -196,9 +280,12 @@ The bot automatically detects @mentions in your message and includes them in its
|
|
|
196
280
|
|
|
197
281
|
Check the following:
|
|
198
282
|
1. Have you configured **event subscriptions**? (See Event Subscriptions section)
|
|
199
|
-
2.
|
|
283
|
+
2. Does the event subscription mode match your `connectionMode`?
|
|
284
|
+
- `websocket` → **Long connection** in Feishu console
|
|
285
|
+
- `webhook` → **Request URL** in Feishu console (URL must be reachable)
|
|
200
286
|
3. Did you add the `im.message.receive_v1` event?
|
|
201
287
|
4. Are the permissions approved?
|
|
288
|
+
5. For webhook mode: is your server running and the URL publicly accessible?
|
|
202
289
|
|
|
203
290
|
#### 403 error when sending messages
|
|
204
291
|
|
|
@@ -339,7 +426,9 @@ openclaw plugins update feishu
|
|
|
339
426
|
|
|
340
427
|
在飞书开放平台的应用后台,进入 **事件与回调** 页面:
|
|
341
428
|
|
|
342
|
-
1.
|
|
429
|
+
1. **事件配置方式**:根据你的 `connectionMode` 选择对应的订阅方式:
|
|
430
|
+
- **使用长连接接收事件** — 对应 `connectionMode: "websocket"`(推荐,无需公网地址)
|
|
431
|
+
- **使用请求地址接收事件** — 对应 `connectionMode: "webhook"`(需要公网可访问的 URL)
|
|
343
432
|
2. **添加事件订阅**,勾选以下事件:
|
|
344
433
|
|
|
345
434
|
| 事件 | 说明 |
|
|
@@ -365,8 +454,8 @@ channels:
|
|
|
365
454
|
enabled: true
|
|
366
455
|
appId: "cli_xxxxx"
|
|
367
456
|
appSecret: "secret"
|
|
368
|
-
# 域名: "feishu" (国内)
|
|
369
|
-
domain: "feishu"
|
|
457
|
+
# 域名: "feishu" (国内)、"lark" (国际) 或自定义 URL
|
|
458
|
+
domain: "feishu" # 私有化部署可用 "https://open.xxx.cn"
|
|
370
459
|
# 连接模式: "websocket" (推荐) 或 "webhook"
|
|
371
460
|
connectionMode: "websocket"
|
|
372
461
|
# 私聊策略: "pairing" | "open" | "allowlist"
|
|
@@ -381,6 +470,45 @@ channels:
|
|
|
381
470
|
renderMode: "auto"
|
|
382
471
|
```
|
|
383
472
|
|
|
473
|
+
#### 连接模式
|
|
474
|
+
|
|
475
|
+
支持两种从飞书接收事件的连接模式:
|
|
476
|
+
|
|
477
|
+
| 模式 | 说明 |
|
|
478
|
+
|------|------|
|
|
479
|
+
| `websocket` | (默认,推荐)长连接 WebSocket 模式。无需公网地址,可在 NAT/防火墙后使用。适合本地开发和大部分部署场景。 |
|
|
480
|
+
| `webhook` | HTTP 服务器接收事件回调。需要公网可访问的 URL。适合通过反向代理(如 Nginx)部署的服务器环境。 |
|
|
481
|
+
|
|
482
|
+
**WebSocket 模式**(默认,无需额外配置):
|
|
483
|
+
|
|
484
|
+
```yaml
|
|
485
|
+
channels:
|
|
486
|
+
feishu:
|
|
487
|
+
connectionMode: "websocket" # 或直接省略此行
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
飞书控制台:事件与回调 → 选择 **使用长连接接收事件**。
|
|
491
|
+
|
|
492
|
+
**Webhook 模式**:
|
|
493
|
+
|
|
494
|
+
```yaml
|
|
495
|
+
channels:
|
|
496
|
+
feishu:
|
|
497
|
+
connectionMode: "webhook"
|
|
498
|
+
webhookPort: 3000 # HTTP 服务端口(默认: 3000)
|
|
499
|
+
webhookPath: "/feishu/events" # 事件回调路径(默认: "/feishu/events")
|
|
500
|
+
encryptKey: "your_encrypt_key" # 飞书控制台 → 事件与回调 → Encrypt Key
|
|
501
|
+
verificationToken: "your_verify_token" # 飞书控制台 → 事件与回调 → Verification Token
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
飞书控制台:事件与回调 → 选择 **使用请求地址接收事件** → 填入请求地址:
|
|
505
|
+
|
|
506
|
+
```
|
|
507
|
+
https://your-domain.com/feishu/events
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
> **提示:** 请求地址必须是 HTTPS 且公网可访问。本地开发时,可使用 [ngrok](https://ngrok.com) 等工具创建隧道:`ngrok http 3000`,然后使用生成的地址。
|
|
511
|
+
|
|
384
512
|
#### 渲染模式
|
|
385
513
|
|
|
386
514
|
| 模式 | 说明 |
|
|
@@ -389,6 +517,46 @@ channels:
|
|
|
389
517
|
| `raw` | 始终纯文本,表格转为 ASCII |
|
|
390
518
|
| `card` | 始终使用卡片,支持语法高亮、表格、链接等 |
|
|
391
519
|
|
|
520
|
+
#### 动态 Agent 创建(多用户 Workspace 隔离)
|
|
521
|
+
|
|
522
|
+
启用后,每个私聊用户会自动获得独立的 agent 实例和专属 workspace。这提供完整的隔离,包括独立的对话历史、记忆(MEMORY.md)和工作区文件。
|
|
523
|
+
|
|
524
|
+
```yaml
|
|
525
|
+
channels:
|
|
526
|
+
feishu:
|
|
527
|
+
dmPolicy: "open"
|
|
528
|
+
allowFrom: ["*"]
|
|
529
|
+
dynamicAgentCreation:
|
|
530
|
+
enabled: true
|
|
531
|
+
# workspace 目录模板 ({userId} = OpenID, {agentId} = 生成的 agent ID)
|
|
532
|
+
workspaceTemplate: "~/workspaces/feishu-{agentId}"
|
|
533
|
+
# agent 配置目录模板
|
|
534
|
+
agentDirTemplate: "~/.openclaw/agents/{agentId}/agent"
|
|
535
|
+
# 可选:限制动态 agent 总数
|
|
536
|
+
maxAgents: 100
|
|
537
|
+
|
|
538
|
+
session:
|
|
539
|
+
# 同时设置 dmScope 以隔离对话历史
|
|
540
|
+
dmScope: "per-peer"
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
| 选项 | 说明 |
|
|
544
|
+
|------|------|
|
|
545
|
+
| `enabled` | 是否为私聊用户启用动态 agent 创建 |
|
|
546
|
+
| `workspaceTemplate` | workspace 路径模板,支持 `{userId}`(OpenID)和 `{agentId}`(= `feishu-{openId}`)|
|
|
547
|
+
| `agentDirTemplate` | agent 目录路径模板 |
|
|
548
|
+
| `maxAgents` | 可选,限制动态 agent 的最大数量 |
|
|
549
|
+
|
|
550
|
+
**工作原理:**
|
|
551
|
+
1. 当新用户发送私聊时,系统在 `openclaw.json` 中创建新的 agent 条目
|
|
552
|
+
2. 创建 binding 将该用户的私聊路由到专属 agent
|
|
553
|
+
3. 自动创建 workspace 和 agent 目录
|
|
554
|
+
4. 该用户后续的消息都会路由到其隔离的 agent
|
|
555
|
+
|
|
556
|
+
**与 `dmScope: "per-peer"` 的区别:**
|
|
557
|
+
- `dmScope: "per-peer"` 仅隔离对话历史
|
|
558
|
+
- `dynamicAgentCreation` 提供完整隔离(workspace、记忆、身份、工具)
|
|
559
|
+
|
|
392
560
|
### 功能
|
|
393
561
|
|
|
394
562
|
- WebSocket 和 Webhook 连接模式
|
|
@@ -406,6 +574,7 @@ channels:
|
|
|
406
574
|
- **多维表格工具**:读写多维表格记录,支持 `/base/` 和 `/wiki/` 两种链接格式
|
|
407
575
|
- **@ 转发功能**:在消息中 @ 某人,机器人的回复会自动 @ 该用户
|
|
408
576
|
- **权限错误提示**:当机器人遇到飞书 API 权限错误时,会自动通知用户并提供权限授权链接
|
|
577
|
+
- **动态 Agent 创建**:每个私聊用户可拥有独立的 agent 实例和专属 workspace(可选)
|
|
409
578
|
|
|
410
579
|
#### @ 转发功能
|
|
411
580
|
|
|
@@ -422,9 +591,12 @@ channels:
|
|
|
422
591
|
|
|
423
592
|
检查以下配置:
|
|
424
593
|
1. 是否配置了 **事件订阅**?(见上方事件订阅章节)
|
|
425
|
-
2.
|
|
594
|
+
2. 事件订阅方式是否与 `connectionMode` 匹配?
|
|
595
|
+
- `websocket` → 飞书控制台选择 **使用长连接接收事件**
|
|
596
|
+
- `webhook` → 飞书控制台选择 **使用请求地址接收事件**(URL 必须可访问)
|
|
426
597
|
3. 是否添加了 `im.message.receive_v1` 事件?
|
|
427
598
|
4. 相关权限是否已申请并审核通过?
|
|
599
|
+
5. 如果使用 webhook 模式:服务是否正在运行?URL 是否公网可访问?
|
|
428
600
|
|
|
429
601
|
#### 返回消息时 403 错误
|
|
430
602
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m1heng-clawd/feishu",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw Feishu/Lark channel plugin",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@larksuiteoapi/node-sdk": "^1.
|
|
49
|
+
"@larksuiteoapi/node-sdk": "^1.56.1",
|
|
50
50
|
"@sinclair/typebox": "^0.34.48",
|
|
51
51
|
"zod": "^4.3.6"
|
|
52
52
|
},
|
package/src/accounts.ts
CHANGED
|
@@ -1,7 +1,79 @@
|
|
|
1
1
|
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
-
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
|
|
3
|
-
import type { FeishuConfig, FeishuDomain, ResolvedFeishuAccount } from "./types.js";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
|
|
3
|
+
import type { FeishuConfig, FeishuAccountConfig, FeishuDomain, ResolvedFeishuAccount } from "./types.js";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* List all configured account IDs from the accounts field.
|
|
7
|
+
*/
|
|
8
|
+
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
|
9
|
+
const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
|
|
10
|
+
if (!accounts || typeof accounts !== "object") {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
return Object.keys(accounts).filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* List all Feishu account IDs.
|
|
18
|
+
* If no accounts are configured, returns [DEFAULT_ACCOUNT_ID] for backward compatibility.
|
|
19
|
+
*/
|
|
20
|
+
export function listFeishuAccountIds(cfg: ClawdbotConfig): string[] {
|
|
21
|
+
const ids = listConfiguredAccountIds(cfg);
|
|
22
|
+
if (ids.length === 0) {
|
|
23
|
+
// Backward compatibility: no accounts configured, use default
|
|
24
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
25
|
+
}
|
|
26
|
+
return [...ids].sort((a, b) => a.localeCompare(b));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the default account ID.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string {
|
|
33
|
+
const ids = listFeishuAccountIds(cfg);
|
|
34
|
+
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
35
|
+
return DEFAULT_ACCOUNT_ID;
|
|
36
|
+
}
|
|
37
|
+
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the raw account-specific config.
|
|
42
|
+
*/
|
|
43
|
+
function resolveAccountConfig(
|
|
44
|
+
cfg: ClawdbotConfig,
|
|
45
|
+
accountId: string,
|
|
46
|
+
): FeishuAccountConfig | undefined {
|
|
47
|
+
const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
|
|
48
|
+
if (!accounts || typeof accounts !== "object") {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return accounts[accountId];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Merge top-level config with account-specific config.
|
|
56
|
+
* Account-specific fields override top-level fields.
|
|
57
|
+
*/
|
|
58
|
+
function mergeFeishuAccountConfig(
|
|
59
|
+
cfg: ClawdbotConfig,
|
|
60
|
+
accountId: string,
|
|
61
|
+
): FeishuConfig {
|
|
62
|
+
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
63
|
+
|
|
64
|
+
// Extract base config (exclude accounts field to avoid recursion)
|
|
65
|
+
const { accounts: _ignored, ...base } = feishuCfg ?? {};
|
|
66
|
+
|
|
67
|
+
// Get account-specific overrides
|
|
68
|
+
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
69
|
+
|
|
70
|
+
// Merge: account config overrides base config
|
|
71
|
+
return { ...base, ...account } as FeishuConfig;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve Feishu credentials from a config.
|
|
76
|
+
*/
|
|
5
77
|
export function resolveFeishuCredentials(cfg?: FeishuConfig): {
|
|
6
78
|
appId: string;
|
|
7
79
|
appSecret: string;
|
|
@@ -21,31 +93,46 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
|
|
|
21
93
|
};
|
|
22
94
|
}
|
|
23
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Resolve a complete Feishu account with merged config.
|
|
98
|
+
*/
|
|
24
99
|
export function resolveFeishuAccount(params: {
|
|
25
100
|
cfg: ClawdbotConfig;
|
|
26
101
|
accountId?: string | null;
|
|
27
102
|
}): ResolvedFeishuAccount {
|
|
103
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
28
104
|
const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
|
|
29
|
-
|
|
30
|
-
|
|
105
|
+
|
|
106
|
+
// Base enabled state (top-level)
|
|
107
|
+
const baseEnabled = feishuCfg?.enabled !== false;
|
|
108
|
+
|
|
109
|
+
// Merge configs
|
|
110
|
+
const merged = mergeFeishuAccountConfig(params.cfg, accountId);
|
|
111
|
+
|
|
112
|
+
// Account-level enabled state
|
|
113
|
+
const accountEnabled = merged.enabled !== false;
|
|
114
|
+
const enabled = baseEnabled && accountEnabled;
|
|
115
|
+
|
|
116
|
+
// Resolve credentials from merged config
|
|
117
|
+
const creds = resolveFeishuCredentials(merged);
|
|
31
118
|
|
|
32
119
|
return {
|
|
33
|
-
accountId
|
|
120
|
+
accountId,
|
|
34
121
|
enabled,
|
|
35
122
|
configured: Boolean(creds),
|
|
123
|
+
name: (merged as FeishuAccountConfig).name?.trim() || undefined,
|
|
36
124
|
appId: creds?.appId,
|
|
125
|
+
appSecret: creds?.appSecret,
|
|
126
|
+
encryptKey: creds?.encryptKey,
|
|
127
|
+
verificationToken: creds?.verificationToken,
|
|
37
128
|
domain: creds?.domain ?? "feishu",
|
|
129
|
+
config: merged,
|
|
38
130
|
};
|
|
39
131
|
}
|
|
40
132
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
export function resolveDefaultFeishuAccountId(_cfg: ClawdbotConfig): string {
|
|
46
|
-
return DEFAULT_ACCOUNT_ID;
|
|
47
|
-
}
|
|
48
|
-
|
|
133
|
+
/**
|
|
134
|
+
* List all enabled and configured accounts.
|
|
135
|
+
*/
|
|
49
136
|
export function listEnabledFeishuAccounts(cfg: ClawdbotConfig): ResolvedFeishuAccount[] {
|
|
50
137
|
return listFeishuAccountIds(cfg)
|
|
51
138
|
.map((accountId) => resolveFeishuAccount({ cfg, accountId }))
|