@larksuite/openclaw-lark 2026.4.8 → 2026.4.9
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/package.json +2 -1
- package/skills/feishu-task/SKILL.md +79 -32
- package/src/channel/event-handlers.d.ts +1 -0
- package/src/channel/event-handlers.js +73 -0
- package/src/channel/monitor.js +1 -0
- package/src/core/raw-request.js +26 -2
- package/src/core/synthetic-target.d.ts +33 -0
- package/src/core/synthetic-target.js +40 -0
- package/src/core/targets.js +0 -1
- package/src/core/tool-scopes.d.ts +2 -2
- package/src/core/tool-scopes.js +6 -1
- package/src/messaging/inbound/dispatch.d.ts +2 -0
- package/src/messaging/inbound/dispatch.js +76 -0
- package/src/messaging/inbound/vc-meeting-invited-handler.d.ts +20 -0
- package/src/messaging/inbound/vc-meeting-invited-handler.js +226 -0
- package/src/messaging/inbound/vc-sender.d.ts +41 -0
- package/src/messaging/inbound/vc-sender.js +53 -0
- package/src/messaging/outbound/outbound.js +18 -0
- package/src/messaging/types.d.ts +63 -0
- package/src/tools/oapi/index.js +2 -0
- package/src/tools/oapi/task/attachment.d.ts +18 -0
- package/src/tools/oapi/task/attachment.js +107 -0
- package/src/tools/oapi/task/comment.d.ts +1 -1
- package/src/tools/oapi/task/comment.js +11 -5
- package/src/tools/oapi/task/index.d.ts +2 -0
- package/src/tools/oapi/task/index.js +5 -1
- package/src/tools/oapi/task/section.d.ts +1 -1
- package/src/tools/oapi/task/section.js +15 -7
- package/src/tools/oapi/task/subtask.d.ts +1 -1
- package/src/tools/oapi/task/subtask.js +12 -6
- package/src/tools/oapi/task/task.d.ts +3 -0
- package/src/tools/oapi/task/task.js +174 -9
- package/src/tools/oapi/task/task_agent.d.ts +14 -0
- package/src/tools/oapi/task/task_agent.js +108 -0
- package/src/tools/oapi/task/tasklist.d.ts +1 -1
- package/src/tools/oapi/task/tasklist.js +30 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larksuite/openclaw-lark",
|
|
3
|
-
"version": "2026.4.
|
|
3
|
+
"version": "2026.4.9",
|
|
4
4
|
"description": "OpenClaw Lark/Feishu channel plugin",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"@larksuiteoapi/node-sdk": "^1.60.0",
|
|
27
27
|
"@sinclair/typebox": "0.34.48",
|
|
28
28
|
"image-size": "^2.0.2",
|
|
29
|
+
"undici-types": "^8.1.0",
|
|
29
30
|
"zod": "^4.3.6"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
@@ -8,7 +8,10 @@ description: |
|
|
|
8
8
|
(2) 需要创建、管理任务清单
|
|
9
9
|
(3) 需要查看任务列表或清单内的任务
|
|
10
10
|
(4) 用户提到"任务"、"待办"、"to-do"、"清单"、"task"
|
|
11
|
-
(5)
|
|
11
|
+
(5) 需要设置任务负责人、关注人、截止时间、添加成员
|
|
12
|
+
(6) 需要追加任务步骤记录(Task 的 steps)
|
|
13
|
+
(7) 需要上传任务附件(支持 task / task_delivery)
|
|
14
|
+
(8) 需要注册 Agent / 更新 Agent 信息(register / update_profile)
|
|
12
15
|
---
|
|
13
16
|
|
|
14
17
|
# 飞书任务管理
|
|
@@ -16,11 +19,14 @@ description: |
|
|
|
16
19
|
## 🚨 执行前必读
|
|
17
20
|
|
|
18
21
|
- ✅ **时间格式**:ISO 8601 / RFC 3339(带时区),例如 `2026-02-28T17:00:00+08:00`
|
|
22
|
+
- ✅ **身份授权**:工具支持 `auth_type` 为 `user`(默认,用户身份)或 `tenant`(应用身份)。
|
|
23
|
+
- ✅ **任务 Agent(feishu_task_agent)**:仅支持应用身份(tenant),不支持 user 身份
|
|
19
24
|
- ✅ **current_user_id 强烈建议**:从消息上下文的 SenderId 获取(ou_...),工具会自动添加为 follower(如不在 members 中),确保创建者可以编辑任务
|
|
20
25
|
- ✅ **patch/get 必须**:task_guid
|
|
21
26
|
- ✅ **tasklist.tasks 必须**:tasklist_guid
|
|
22
27
|
- ✅ **完成任务**:completed_at = "2026-02-26 15:00:00"
|
|
23
28
|
- ✅ **反完成(恢复未完成)**:completed_at = "0"
|
|
29
|
+
- ✅ **append_steps 的 task_steps[].timestamp**:秒级 Unix 时间戳(10 位),不要用毫秒(13 位)
|
|
24
30
|
|
|
25
31
|
---
|
|
26
32
|
|
|
@@ -28,52 +34,58 @@ description: |
|
|
|
28
34
|
|
|
29
35
|
| 用户意图 | 工具 | action | 必填参数 | 强烈建议 | 常用可选 |
|
|
30
36
|
|---------|------|--------|---------|---------|---------|
|
|
31
|
-
| 新建待办 | feishu_task_task | create | summary | current_user_id(SenderId) | members, due, description |
|
|
32
|
-
| 查未完成任务 | feishu_task_task | list | - | completed=false | page_size |
|
|
33
|
-
| 获取任务详情 | feishu_task_task | get | task_guid | - |
|
|
34
|
-
| 完成任务 | feishu_task_task | patch | task_guid, completed_at | - |
|
|
35
|
-
| 反完成任务 | feishu_task_task | patch | task_guid, completed_at="0" | - |
|
|
36
|
-
| 改截止时间 | feishu_task_task | patch | task_guid, due | - |
|
|
37
|
+
| 新建待办 | feishu_task_task | create | summary | current_user_id(SenderId) | members, due, description, auth_type |
|
|
38
|
+
| 查未完成任务 | feishu_task_task | list | - | completed=false | page_size, auth_type, agent_task_status |
|
|
39
|
+
| 获取任务详情 | feishu_task_task | get | task_guid | - | auth_type |
|
|
40
|
+
| 完成任务 | feishu_task_task | patch | task_guid, completed_at | - | auth_type |
|
|
41
|
+
| 反完成任务 | feishu_task_task | patch | task_guid, completed_at="0" | - | auth_type |
|
|
42
|
+
| 改截止时间 | feishu_task_task | patch | task_guid, due | - | auth_type |
|
|
43
|
+
| 添加任务成员 | feishu_task_task | add_members | task_guid, members[] | - | auth_type |
|
|
44
|
+
| 追加任务步骤记录 | feishu_task_task | append_steps | task_guid, idempotent_key, task_steps[] | task_steps[].timestamp 用秒级(10 位) | - |
|
|
37
45
|
| 创建清单 | feishu_task_tasklist | create | name | - | members |
|
|
38
46
|
| 查看清单任务 | feishu_task_tasklist | tasks | tasklist_guid | - | completed |
|
|
39
47
|
| 添加清单成员 | feishu_task_tasklist | add_members | tasklist_guid, members[] | - | - |
|
|
48
|
+
| 上传任务附件 | feishu_task_attachment | upload | resource_id, file(base64) | name | resource_type |
|
|
49
|
+
| 注册任务 Agent | feishu_task_agent | register | - | 仅支持 tenant(应用身份) | - |
|
|
50
|
+
| 更新任务 Agent Profile | feishu_task_agent | update_profile | profile_content | 仅支持 tenant(应用身份) | - |
|
|
40
51
|
|
|
41
52
|
---
|
|
42
53
|
|
|
43
54
|
## 🎯 核心约束(Schema 未透露的知识)
|
|
44
55
|
|
|
45
|
-
### 1.
|
|
56
|
+
### 1. 授权身份与可见性 (auth_type)
|
|
46
57
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
- ⚠️ **如果创建时没把自己加入成员,后续无法编辑该任务**
|
|
58
|
+
**工具支持两种调用身份 `auth_type`**:
|
|
59
|
+
- **`user` (默认)**:用户身份(user_access_token)。用于需要严格代表用户操作或查询用户私有任务的场景。
|
|
60
|
+
- ⚠️ 使用 `user` 身份时,只能查看和编辑**自己是成员的任务**。
|
|
61
|
+
- ⚠️ **如果创建时没把自己加入成员,后续无法编辑该任务**。
|
|
62
|
+
- **`tenant`**:应用身份(tenant_access_token)。当用户身份不满足要求时,使用应用身份。如果创建的任务没有把用户加入成员,用户可能看不见。
|
|
53
63
|
|
|
54
64
|
**自动保护机制**:
|
|
55
65
|
- 传入 `current_user_id` 参数(从 SenderId 获取)
|
|
56
66
|
- 如果 `members` 中不包含 `current_user_id`,工具会**自动添加为 follower**
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
**推荐用法**:创建任务时始终传 `current_user_id`,工具会自动处理成员关系。
|
|
67
|
+
- 确保创建者始终可以编辑和查看任务
|
|
60
68
|
|
|
61
|
-
### 2.
|
|
69
|
+
### 2. 任务成员的角色与类型
|
|
62
70
|
|
|
63
|
-
-
|
|
64
|
-
- **
|
|
71
|
+
- **角色 (role)**:
|
|
72
|
+
- **assignee(负责人)**:负责完成任务,可以编辑任务
|
|
73
|
+
- **follower(关注人)**:关注任务进展,接收通知
|
|
74
|
+
- **类型 (type)**:
|
|
75
|
+
- **user(默认,用户)**:普通的飞书用户
|
|
76
|
+
- **app(应用/机器人)**:如果是把机器人自己或者其他应用加入任务,必须指定 `type: "app"`
|
|
65
77
|
|
|
66
78
|
**添加成员示例**:
|
|
67
79
|
```json
|
|
68
80
|
{
|
|
69
81
|
"members": [
|
|
70
|
-
{"id": "ou_xxx", "role": "assignee"}, //
|
|
71
|
-
{"id": "
|
|
82
|
+
{"id": "ou_xxx", "role": "assignee", "type": "user"}, // 负责人(用户)
|
|
83
|
+
{"id": "cli_yyy", "role": "follower", "type": "app"} // 关注人(机器人/应用)
|
|
72
84
|
]
|
|
73
85
|
}
|
|
74
86
|
```
|
|
75
87
|
|
|
76
|
-
**说明**:`id`
|
|
88
|
+
**说明**:`id` 默认使用 `open_id`。
|
|
77
89
|
|
|
78
90
|
### 3. 任务清单角色冲突
|
|
79
91
|
|
|
@@ -133,12 +145,13 @@ description: |
|
|
|
133
145
|
"summary": "准备周会材料",
|
|
134
146
|
"description": "整理本周工作进展和下周计划",
|
|
135
147
|
"current_user_id": "ou_发送者的open_id",
|
|
148
|
+
"auth_type": "tenant",
|
|
136
149
|
"due": {
|
|
137
150
|
"timestamp": "2026-02-28 17:00:00",
|
|
138
151
|
"is_all_day": false
|
|
139
152
|
},
|
|
140
153
|
"members": [
|
|
141
|
-
{"id": "ou_协作者的open_id", "role": "assignee"}
|
|
154
|
+
{"id": "ou_协作者的open_id", "role": "assignee", "type": "user"}
|
|
142
155
|
]
|
|
143
156
|
}
|
|
144
157
|
```
|
|
@@ -147,7 +160,7 @@ description: |
|
|
|
147
160
|
- `summary` 是必填字段
|
|
148
161
|
- `current_user_id` 强烈建议传入(从 SenderId 获取),工具会自动添加为 follower
|
|
149
162
|
- `members` 可以只包含其他协作者,当前用户会被自动添加
|
|
150
|
-
-
|
|
163
|
+
- 时间使用带时区的 ISO 8601 格式
|
|
151
164
|
|
|
152
165
|
### 场景 2: 查询我负责的未完成任务
|
|
153
166
|
|
|
@@ -155,11 +168,25 @@ description: |
|
|
|
155
168
|
{
|
|
156
169
|
"action": "list",
|
|
157
170
|
"completed": false,
|
|
158
|
-
"page_size": 20
|
|
171
|
+
"page_size": 20,
|
|
172
|
+
"auth_type": "user"
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 场景 3: 为现有任务添加机器人或成员
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"action": "add_members",
|
|
181
|
+
"task_guid": "任务的guid",
|
|
182
|
+
"auth_type": "tenant",
|
|
183
|
+
"members": [
|
|
184
|
+
{"id": "cli_机器人的app_id", "role": "follower", "type": "app"}
|
|
185
|
+
]
|
|
159
186
|
}
|
|
160
187
|
```
|
|
161
188
|
|
|
162
|
-
### 场景
|
|
189
|
+
### 场景 4: 完成任务
|
|
163
190
|
|
|
164
191
|
```json
|
|
165
192
|
{
|
|
@@ -169,7 +196,7 @@ description: |
|
|
|
169
196
|
}
|
|
170
197
|
```
|
|
171
198
|
|
|
172
|
-
### 场景
|
|
199
|
+
### 场景 5: 反完成任务(恢复未完成状态)
|
|
173
200
|
|
|
174
201
|
```json
|
|
175
202
|
{
|
|
@@ -179,7 +206,7 @@ description: |
|
|
|
179
206
|
}
|
|
180
207
|
```
|
|
181
208
|
|
|
182
|
-
### 场景
|
|
209
|
+
### 场景 6: 创建清单并添加协作者
|
|
183
210
|
|
|
184
211
|
```json
|
|
185
212
|
{
|
|
@@ -192,7 +219,7 @@ description: |
|
|
|
192
219
|
}
|
|
193
220
|
```
|
|
194
221
|
|
|
195
|
-
### 场景
|
|
222
|
+
### 场景 7: 查看清单内的未完成任务
|
|
196
223
|
|
|
197
224
|
```json
|
|
198
225
|
{
|
|
@@ -202,7 +229,7 @@ description: |
|
|
|
202
229
|
}
|
|
203
230
|
```
|
|
204
231
|
|
|
205
|
-
### 场景
|
|
232
|
+
### 场景 8: 全天任务
|
|
206
233
|
|
|
207
234
|
```json
|
|
208
235
|
{
|
|
@@ -215,6 +242,25 @@ description: |
|
|
|
215
242
|
}
|
|
216
243
|
```
|
|
217
244
|
|
|
245
|
+
### 场景 9: 注册任务 Agent(仅应用身份)
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"action": "register",
|
|
250
|
+
"auth_type": "tenant"
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 场景 10: 更新任务 Agent Profile(仅应用身份)
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"action": "update_profile",
|
|
259
|
+
"auth_type": "tenant",
|
|
260
|
+
"profile_content": "some profile content"
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
218
264
|
---
|
|
219
265
|
|
|
220
266
|
## 🔍 常见错误与排查
|
|
@@ -222,10 +268,11 @@ description: |
|
|
|
222
268
|
| 错误现象 | 根本原因 | 解决方案 |
|
|
223
269
|
|---------|---------|---------|
|
|
224
270
|
| **创建后无法编辑任务** | 创建时未将自己加入 members | 创建时至少将当前用户(SenderId)加为 assignee 或 follower |
|
|
225
|
-
| **patch 失败提示 task_guid 缺失** | 未传 task_guid 参数 | patch/get 必须传 task_guid |
|
|
271
|
+
| **patch 失败提示 task_guid 缺失** | 未传 task_guid 参数 | patch/get/add_members 必须传 task_guid |
|
|
226
272
|
| **tasks 失败提示 tasklist_guid 缺失** | 未传 tasklist_guid 参数 | tasklist.tasks action 必须传 tasklist_guid |
|
|
227
273
|
| **反完成失败** | completed_at 格式错误 | 使用 `"0"` 字符串,不是数字 0 |
|
|
228
274
|
| **时间不对** | 使用了 Unix 时间戳 | 改用 ISO 8601 格式(带时区):`2024-01-01T00:00:00+08:00` |
|
|
275
|
+
| **添加机器人失败** | 未指定成员 type 为 app | 将机器人的 type 指定为 `"app"` |
|
|
229
276
|
|
|
230
277
|
---
|
|
231
278
|
|
|
@@ -12,5 +12,6 @@ import type { MonitorContext } from './types';
|
|
|
12
12
|
export declare function handleMessageEvent(ctx: MonitorContext, data: unknown): Promise<void>;
|
|
13
13
|
export declare function handleReactionEvent(ctx: MonitorContext, data: unknown): Promise<void>;
|
|
14
14
|
export declare function handleBotMembershipEvent(ctx: MonitorContext, data: unknown, action: 'added' | 'removed'): Promise<void>;
|
|
15
|
+
export declare function handleVcMeetingInvitedEvent(ctx: MonitorContext, data: unknown): Promise<void>;
|
|
15
16
|
export declare function handleCommentEvent(ctx: MonitorContext, data: unknown): Promise<void>;
|
|
16
17
|
export declare function handleCardActionEvent(ctx: MonitorContext, data: unknown): Promise<unknown>;
|
|
@@ -13,11 +13,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
13
13
|
exports.handleMessageEvent = handleMessageEvent;
|
|
14
14
|
exports.handleReactionEvent = handleReactionEvent;
|
|
15
15
|
exports.handleBotMembershipEvent = handleBotMembershipEvent;
|
|
16
|
+
exports.handleVcMeetingInvitedEvent = handleVcMeetingInvitedEvent;
|
|
16
17
|
exports.handleCommentEvent = handleCommentEvent;
|
|
17
18
|
exports.handleCardActionEvent = handleCardActionEvent;
|
|
18
19
|
const handler_1 = require("../messaging/inbound/handler.js");
|
|
19
20
|
const reaction_handler_1 = require("../messaging/inbound/reaction-handler.js");
|
|
20
21
|
const comment_handler_1 = require("../messaging/inbound/comment-handler.js");
|
|
22
|
+
const vc_meeting_invited_handler_1 = require("../messaging/inbound/vc-meeting-invited-handler.js");
|
|
23
|
+
const vc_sender_1 = require("../messaging/inbound/vc-sender.js");
|
|
21
24
|
const comment_context_1 = require("../messaging/inbound/comment-context.js");
|
|
22
25
|
const dedup_1 = require("../messaging/inbound/dedup.js");
|
|
23
26
|
const lark_ticket_1 = require("../core/lark-ticket.js");
|
|
@@ -223,6 +226,76 @@ async function handleBotMembershipEvent(ctx, data, action) {
|
|
|
223
226
|
}
|
|
224
227
|
}
|
|
225
228
|
// ---------------------------------------------------------------------------
|
|
229
|
+
// VC meeting invited handler
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
async function handleVcMeetingInvitedEvent(ctx, data) {
|
|
232
|
+
if (!isEventOwnershipValid(ctx, data))
|
|
233
|
+
return;
|
|
234
|
+
const { accountId, log, error } = ctx;
|
|
235
|
+
try {
|
|
236
|
+
const event = data;
|
|
237
|
+
const meetingNo = event.meeting?.meeting_no?.trim() ?? '';
|
|
238
|
+
const eventId = event.event_id?.trim() ?? '';
|
|
239
|
+
// Resolve the inviter identity through the shared helper so the
|
|
240
|
+
// diagnostics log and the dispatch handler always agree on the
|
|
241
|
+
// same sender semantics.
|
|
242
|
+
const sender = (0, vc_sender_1.resolveVcSender)(event);
|
|
243
|
+
const senderId = sender.senderId;
|
|
244
|
+
const invitedBotOpenId = event.bot?.id?.open_id?.trim() ?? '';
|
|
245
|
+
// VC invited origin/ownership diagnostics:
|
|
246
|
+
// - This handler is only reachable from the WebSocket monitor path.
|
|
247
|
+
// - We still log app_id/bot_open_id so operators can confirm the event
|
|
248
|
+
// is delivered to the expected bot/account, and see which required
|
|
249
|
+
// fields are missing when we skip.
|
|
250
|
+
const expectedAppId = ctx.lark.account.appId ?? '';
|
|
251
|
+
const eventAppId = event.app_id?.trim() ?? '';
|
|
252
|
+
log(`feishu[${accountId}]: vc invited event received (ingress=websocket)` +
|
|
253
|
+
`${eventId ? ` event_id=${eventId}` : ''}` +
|
|
254
|
+
`${eventAppId ? ` app_id=${eventAppId}` : ' app_id=<missing>'}` +
|
|
255
|
+
`${expectedAppId ? ` expected_app_id=${expectedAppId}` : ''}` +
|
|
256
|
+
`${invitedBotOpenId ? ` bot_open_id=${invitedBotOpenId}` : ' bot_open_id=<missing>'}` +
|
|
257
|
+
`${ctx.lark.botOpenId ? ` expected_bot_open_id=${ctx.lark.botOpenId}` : ''}` +
|
|
258
|
+
`${event.invite_time ? ` invite_time=${event.invite_time}` : ''}` +
|
|
259
|
+
` meeting_no_present=${meetingNo ? 'true' : 'false'}` +
|
|
260
|
+
` sender_present=${senderId ? 'true' : 'false'}` +
|
|
261
|
+
` sender_from=${sender.fromFallback}`);
|
|
262
|
+
if (!meetingNo) {
|
|
263
|
+
log(`feishu[${accountId}]: vc invited event missing meeting_no, skipping`);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (!senderId) {
|
|
267
|
+
log(`feishu[${accountId}]: vc invited event missing inviter identity, skipping`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (ctx.lark.botOpenId && invitedBotOpenId && invitedBotOpenId !== ctx.lark.botOpenId) {
|
|
271
|
+
log(`feishu[${accountId}]: vc invited event for another bot, expected=${ctx.lark.botOpenId}, got=${invitedBotOpenId}, skipping`);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
// Prefer event_id when the SDK exposes it: historical raw payload logs
|
|
275
|
+
// show WebSocket reconnect replays reuse the same event_id, while a real
|
|
276
|
+
// second invitation yields a new event_id even for the same meeting/bot.
|
|
277
|
+
// Fallback to (meeting_no, bot) only when event_id is absent so older
|
|
278
|
+
// payload shapes still remain deduplicated.
|
|
279
|
+
const dedupBotKey = ctx.lark.botOpenId ?? invitedBotOpenId ?? 'no-bot';
|
|
280
|
+
const dedupKey = eventId ? `vc-invited:by-event:${eventId}` : `vc-invited:by-meeting:${meetingNo}:${dedupBotKey}`;
|
|
281
|
+
if (!ctx.messageDedup.tryRecord(dedupKey, accountId)) {
|
|
282
|
+
log(`feishu[${accountId}]: duplicate vc invited event detected, skipping`);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
log(`feishu[${accountId}]: vc invited event accepted for synthetic dispatch`);
|
|
286
|
+
await (0, vc_meeting_invited_handler_1.handleFeishuVcMeetingInvited)({
|
|
287
|
+
cfg: ctx.cfg,
|
|
288
|
+
event,
|
|
289
|
+
runtime: ctx.runtime,
|
|
290
|
+
chatHistories: ctx.chatHistories,
|
|
291
|
+
accountId,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
error(`feishu[${accountId}]: error handling vc invited event: ${String(err)}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
226
299
|
// Drive comment handler
|
|
227
300
|
// ---------------------------------------------------------------------------
|
|
228
301
|
async function handleCommentEvent(ctx, data) {
|
package/src/channel/monitor.js
CHANGED
|
@@ -77,6 +77,7 @@ async function monitorSingleAccount(params) {
|
|
|
77
77
|
'im.chat.access_event.bot_p2p_chat_entered_v1': async () => { },
|
|
78
78
|
'im.chat.member.bot.added_v1': (data) => (0, event_handlers_1.handleBotMembershipEvent)(ctx, data, 'added'),
|
|
79
79
|
'im.chat.member.bot.deleted_v1': (data) => (0, event_handlers_1.handleBotMembershipEvent)(ctx, data, 'removed'),
|
|
80
|
+
'vc.bot.meeting_invited_v1': (data) => (0, event_handlers_1.handleVcMeetingInvitedEvent)(ctx, data),
|
|
80
81
|
// Drive comment event — fires when a user adds a comment or reply on a document.
|
|
81
82
|
'drive.notice.comment_add_v1': (data) => (0, event_handlers_1.handleCommentEvent)(ctx, data),
|
|
82
83
|
// 飞书 SDK EventDispatcher.register 不支持带返回值的处理器,此处 as any 是 SDK 类型限制的变通
|
package/src/core/raw-request.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.resolveDomainUrl = resolveDomainUrl;
|
|
13
13
|
exports.rawLarkRequest = rawLarkRequest;
|
|
14
|
+
const node_url_1 = require("node:url");
|
|
14
15
|
const feishu_fetch_1 = require("./feishu-fetch.js");
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// Domain URL resolution
|
|
@@ -23,6 +24,24 @@ function resolveDomainUrl(brand) {
|
|
|
23
24
|
};
|
|
24
25
|
return map[brand] ?? `https://${brand}`;
|
|
25
26
|
}
|
|
27
|
+
function isFormDataBody(body) {
|
|
28
|
+
return (typeof body === 'object' &&
|
|
29
|
+
body !== null &&
|
|
30
|
+
typeof body.append === 'function' &&
|
|
31
|
+
typeof body.entries === 'function');
|
|
32
|
+
}
|
|
33
|
+
function isBinaryBody(body) {
|
|
34
|
+
return body instanceof ArrayBuffer || ArrayBuffer.isView(body);
|
|
35
|
+
}
|
|
36
|
+
function buildRequestBody(body) {
|
|
37
|
+
if (typeof body === 'string' || body instanceof node_url_1.URLSearchParams || isFormDataBody(body) || isBinaryBody(body)) {
|
|
38
|
+
return { body: body };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify(body),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
26
45
|
/**
|
|
27
46
|
* 发起 raw HTTP 请求到飞书 API,自动处理域名解析、header 注入和错误检测。
|
|
28
47
|
*
|
|
@@ -37,11 +56,16 @@ async function rawLarkRequest(options) {
|
|
|
37
56
|
}
|
|
38
57
|
}
|
|
39
58
|
const headers = {};
|
|
59
|
+
let requestBody;
|
|
40
60
|
if (options.accessToken) {
|
|
41
61
|
headers['Authorization'] = `Bearer ${options.accessToken}`;
|
|
42
62
|
}
|
|
43
63
|
if (options.body !== undefined) {
|
|
44
|
-
|
|
64
|
+
const prepared = buildRequestBody(options.body);
|
|
65
|
+
requestBody = prepared.body;
|
|
66
|
+
if (prepared.headers) {
|
|
67
|
+
Object.assign(headers, prepared.headers);
|
|
68
|
+
}
|
|
45
69
|
}
|
|
46
70
|
if (options.headers) {
|
|
47
71
|
Object.assign(headers, options.headers);
|
|
@@ -49,7 +73,7 @@ async function rawLarkRequest(options) {
|
|
|
49
73
|
const resp = await (0, feishu_fetch_1.feishuFetch)(url.toString(), {
|
|
50
74
|
method: options.method ?? 'GET',
|
|
51
75
|
headers,
|
|
52
|
-
...(
|
|
76
|
+
...(requestBody !== undefined ? { body: requestBody } : {}),
|
|
53
77
|
});
|
|
54
78
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
79
|
const data = (await resp.json());
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Synthetic IM target utilities.
|
|
6
|
+
*
|
|
7
|
+
* Some inbound flows (VC meeting-invited, other service-triggered events)
|
|
8
|
+
* do not map to a real IM chat — there is no chat_id / open_id the agent
|
|
9
|
+
* should send messages into. To keep the dispatch pipeline uniform we give
|
|
10
|
+
* these flows a sentinel chatId ("synthetic:<kind>") and teach outbound
|
|
11
|
+
* deliverers to short-circuit whenever they see this prefix. This is the
|
|
12
|
+
* same pattern used by `core/comment-target.ts` for Drive comment threads.
|
|
13
|
+
*/
|
|
14
|
+
/** Sentinel chatId for VC `vc.bot.meeting_invited_v1` synthetic inbound. */
|
|
15
|
+
export declare const SYNTHETIC_VC_CHAT_ID = "synthetic:vc-invited";
|
|
16
|
+
/**
|
|
17
|
+
* The `chatType` stamped on synthetic VC contexts.
|
|
18
|
+
*
|
|
19
|
+
* `MessageContext.chatType` is currently typed as `'p2p' | 'group'` across
|
|
20
|
+
* the plugin and widening that union touches every downstream signature.
|
|
21
|
+
* 'p2p' is the closest match (single-peer, non-group) and the outbound
|
|
22
|
+
* short-circuit gates on the sentinel chatId — not the chatType — so this
|
|
23
|
+
* choice does not produce any DMs on its own.
|
|
24
|
+
*
|
|
25
|
+
* TODO(synthetic-target): widen MessageContext.chatType to include
|
|
26
|
+
* `synthetic` once downstream signatures are audited.
|
|
27
|
+
*/
|
|
28
|
+
export declare const SYNTHETIC_VC_CHAT_TYPE: "p2p";
|
|
29
|
+
/**
|
|
30
|
+
* Return `true` when `target` is a synthetic sentinel that outbound
|
|
31
|
+
* deliverers should not try to send an IM message to.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isSyntheticTarget(target: string | undefined | null): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*
|
|
6
|
+
* Synthetic IM target utilities.
|
|
7
|
+
*
|
|
8
|
+
* Some inbound flows (VC meeting-invited, other service-triggered events)
|
|
9
|
+
* do not map to a real IM chat — there is no chat_id / open_id the agent
|
|
10
|
+
* should send messages into. To keep the dispatch pipeline uniform we give
|
|
11
|
+
* these flows a sentinel chatId ("synthetic:<kind>") and teach outbound
|
|
12
|
+
* deliverers to short-circuit whenever they see this prefix. This is the
|
|
13
|
+
* same pattern used by `core/comment-target.ts` for Drive comment threads.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.SYNTHETIC_VC_CHAT_TYPE = exports.SYNTHETIC_VC_CHAT_ID = void 0;
|
|
17
|
+
exports.isSyntheticTarget = isSyntheticTarget;
|
|
18
|
+
const SYNTHETIC_PREFIX = 'synthetic:';
|
|
19
|
+
/** Sentinel chatId for VC `vc.bot.meeting_invited_v1` synthetic inbound. */
|
|
20
|
+
exports.SYNTHETIC_VC_CHAT_ID = 'synthetic:vc-invited';
|
|
21
|
+
/**
|
|
22
|
+
* The `chatType` stamped on synthetic VC contexts.
|
|
23
|
+
*
|
|
24
|
+
* `MessageContext.chatType` is currently typed as `'p2p' | 'group'` across
|
|
25
|
+
* the plugin and widening that union touches every downstream signature.
|
|
26
|
+
* 'p2p' is the closest match (single-peer, non-group) and the outbound
|
|
27
|
+
* short-circuit gates on the sentinel chatId — not the chatType — so this
|
|
28
|
+
* choice does not produce any DMs on its own.
|
|
29
|
+
*
|
|
30
|
+
* TODO(synthetic-target): widen MessageContext.chatType to include
|
|
31
|
+
* `synthetic` once downstream signatures are audited.
|
|
32
|
+
*/
|
|
33
|
+
exports.SYNTHETIC_VC_CHAT_TYPE = 'p2p';
|
|
34
|
+
/**
|
|
35
|
+
* Return `true` when `target` is a synthetic sentinel that outbound
|
|
36
|
+
* deliverers should not try to send an IM message to.
|
|
37
|
+
*/
|
|
38
|
+
function isSyntheticTarget(target) {
|
|
39
|
+
return Boolean(target && target.startsWith(SYNTHETIC_PREFIX));
|
|
40
|
+
}
|
package/src/core/targets.js
CHANGED
|
@@ -145,7 +145,6 @@ function resolveReceiveIdType(id) {
|
|
|
145
145
|
return 'chat_id';
|
|
146
146
|
if (id.startsWith(OPEN_ID_PREFIX))
|
|
147
147
|
return 'open_id';
|
|
148
|
-
// Default to open_id for any other pattern (safer for outbound API calls).
|
|
149
148
|
return 'open_id';
|
|
150
149
|
}
|
|
151
150
|
function normalizeMessageId(messageId) {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
*
|
|
54
54
|
* 总计:96 个工具动作
|
|
55
55
|
*/
|
|
56
|
-
export type ToolActionKey = 'feishu_bitable_app.copy' | 'feishu_bitable_app.create' | 'feishu_bitable_app.get' | 'feishu_bitable_app.list' | 'feishu_bitable_app.patch' | 'feishu_bitable_app_table.batch_create' | 'feishu_bitable_app_table.create' | 'feishu_bitable_app_table.list' | 'feishu_bitable_app_table.patch' | 'feishu_bitable_app_table_field.create' | 'feishu_bitable_app_table_field.delete' | 'feishu_bitable_app_table_field.list' | 'feishu_bitable_app_table_field.update' | 'feishu_bitable_app_table_record.batch_create' | 'feishu_bitable_app_table_record.batch_delete' | 'feishu_bitable_app_table_record.batch_update' | 'feishu_bitable_app_table_record.create' | 'feishu_bitable_app_table_record.delete' | 'feishu_bitable_app_table_record.list' | 'feishu_bitable_app_table_record.update' | 'feishu_bitable_app_table_view.create' | 'feishu_bitable_app_table_view.get' | 'feishu_bitable_app_table_view.list' | 'feishu_bitable_app_table_view.patch' | 'feishu_calendar_calendar.get' | 'feishu_calendar_calendar.list' | 'feishu_calendar_calendar.primary' | 'feishu_calendar_event.create' | 'feishu_calendar_event.delete' | 'feishu_calendar_event.get' | 'feishu_calendar_event.instance_view' | 'feishu_calendar_event.instances' | 'feishu_calendar_event.list' | 'feishu_calendar_event.patch' | 'feishu_calendar_event.reply' | 'feishu_calendar_event.search' | 'feishu_calendar_event_attendee.create' | 'feishu_calendar_event_attendee.list' | 'feishu_calendar_freebusy.list' | 'feishu_chat.get' | 'feishu_chat.search' | 'feishu_chat_members.default' | 'feishu_create_doc.default' | 'feishu_doc_comments.create' | 'feishu_doc_comments.list' | 'feishu_doc_comments.list_replies' | 'feishu_doc_comments.patch' | 'feishu_doc_comments.reply' | 'feishu_doc_media.download' | 'feishu_doc_media.insert' | 'feishu_drive_file.copy' | 'feishu_drive_file.delete' | 'feishu_drive_file.download' | 'feishu_drive_file.get_meta' | 'feishu_drive_file.list' | 'feishu_drive_file.move' | 'feishu_drive_file.upload' | 'feishu_fetch_doc.default' | 'feishu_get_user.basic_batch' | 'feishu_get_user.default' | 'feishu_im_user_fetch_resource.default' | 'feishu_im_user_get_messages.default' | 'feishu_im_user_message.reply' | 'feishu_im_user_message.send' | 'feishu_im_user_search_messages.default' | 'feishu_search_doc_wiki.search' | 'feishu_search_user.default' | 'feishu_task_comment.create' | 'feishu_task_comment.get' | 'feishu_task_comment.list' | 'feishu_task_section.create' | 'feishu_task_section.get' | 'feishu_task_section.list' | 'feishu_task_section.patch' | 'feishu_task_section.tasks' | 'feishu_task_subtask.create' | 'feishu_task_subtask.list' | 'feishu_task_task.create' | 'feishu_task_task.get' | 'feishu_task_task.list' | 'feishu_task_task.patch' | 'feishu_task_tasklist.add_members' | 'feishu_task_tasklist.create' | 'feishu_task_tasklist.get' | 'feishu_task_tasklist.list' | 'feishu_task_tasklist.patch' | 'feishu_task_tasklist.tasks' | 'feishu_update_doc.default' | 'feishu_wiki_space.create' | 'feishu_wiki_space.get' | 'feishu_wiki_space.list' | 'feishu_wiki_space_node.copy' | 'feishu_wiki_space_node.create' | 'feishu_wiki_space_node.get' | 'feishu_wiki_space_node.list' | 'feishu_wiki_space_node.move' | 'feishu_sheet.info' | 'feishu_sheet.read' | 'feishu_sheet.write' | 'feishu_sheet.append' | 'feishu_sheet.find' | 'feishu_sheet.create' | 'feishu_sheet.export';
|
|
56
|
+
export type ToolActionKey = 'feishu_bitable_app.copy' | 'feishu_bitable_app.create' | 'feishu_bitable_app.get' | 'feishu_bitable_app.list' | 'feishu_bitable_app.patch' | 'feishu_bitable_app_table.batch_create' | 'feishu_bitable_app_table.create' | 'feishu_bitable_app_table.list' | 'feishu_bitable_app_table.patch' | 'feishu_bitable_app_table_field.create' | 'feishu_bitable_app_table_field.delete' | 'feishu_bitable_app_table_field.list' | 'feishu_bitable_app_table_field.update' | 'feishu_bitable_app_table_record.batch_create' | 'feishu_bitable_app_table_record.batch_delete' | 'feishu_bitable_app_table_record.batch_update' | 'feishu_bitable_app_table_record.create' | 'feishu_bitable_app_table_record.delete' | 'feishu_bitable_app_table_record.list' | 'feishu_bitable_app_table_record.update' | 'feishu_bitable_app_table_view.create' | 'feishu_bitable_app_table_view.get' | 'feishu_bitable_app_table_view.list' | 'feishu_bitable_app_table_view.patch' | 'feishu_calendar_calendar.get' | 'feishu_calendar_calendar.list' | 'feishu_calendar_calendar.primary' | 'feishu_calendar_event.create' | 'feishu_calendar_event.delete' | 'feishu_calendar_event.get' | 'feishu_calendar_event.instance_view' | 'feishu_calendar_event.instances' | 'feishu_calendar_event.list' | 'feishu_calendar_event.patch' | 'feishu_calendar_event.reply' | 'feishu_calendar_event.search' | 'feishu_calendar_event_attendee.create' | 'feishu_calendar_event_attendee.list' | 'feishu_calendar_freebusy.list' | 'feishu_chat.get' | 'feishu_chat.search' | 'feishu_chat_members.default' | 'feishu_create_doc.default' | 'feishu_doc_comments.create' | 'feishu_doc_comments.list' | 'feishu_doc_comments.list_replies' | 'feishu_doc_comments.patch' | 'feishu_doc_comments.reply' | 'feishu_doc_media.download' | 'feishu_doc_media.insert' | 'feishu_drive_file.copy' | 'feishu_drive_file.delete' | 'feishu_drive_file.download' | 'feishu_drive_file.get_meta' | 'feishu_drive_file.list' | 'feishu_drive_file.move' | 'feishu_drive_file.upload' | 'feishu_fetch_doc.default' | 'feishu_get_user.basic_batch' | 'feishu_get_user.default' | 'feishu_im_user_fetch_resource.default' | 'feishu_im_user_get_messages.default' | 'feishu_im_user_message.reply' | 'feishu_im_user_message.send' | 'feishu_im_user_search_messages.default' | 'feishu_search_doc_wiki.search' | 'feishu_search_user.default' | 'feishu_task_attachment.upload' | 'feishu_task_comment.create' | 'feishu_task_comment.get' | 'feishu_task_comment.list' | 'feishu_task_section.create' | 'feishu_task_section.get' | 'feishu_task_section.list' | 'feishu_task_section.patch' | 'feishu_task_section.tasks' | 'feishu_task_subtask.create' | 'feishu_task_subtask.list' | 'feishu_task_task.create' | 'feishu_task_task.get' | 'feishu_task_task.list' | 'feishu_task_task.patch' | 'feishu_task_task.add_members' | 'feishu_task_task.append_steps' | 'feishu_task_tasklist.add_members' | 'feishu_task_tasklist.create' | 'feishu_task_tasklist.get' | 'feishu_task_tasklist.list' | 'feishu_task_tasklist.patch' | 'feishu_task_tasklist.tasks' | 'feishu_task_agent.register' | 'feishu_task_agent.update_profile' | 'feishu_update_doc.default' | 'feishu_wiki_space.create' | 'feishu_wiki_space.get' | 'feishu_wiki_space.list' | 'feishu_wiki_space_node.copy' | 'feishu_wiki_space_node.create' | 'feishu_wiki_space_node.get' | 'feishu_wiki_space_node.list' | 'feishu_wiki_space_node.move' | 'feishu_sheet.info' | 'feishu_sheet.read' | 'feishu_sheet.write' | 'feishu_sheet.append' | 'feishu_sheet.find' | 'feishu_sheet.create' | 'feishu_sheet.export';
|
|
57
57
|
/**
|
|
58
58
|
* Tool Scope 映射类型
|
|
59
59
|
*
|
|
@@ -153,4 +153,4 @@ export declare function filterSensitiveScopes(scopes: string[]): string[];
|
|
|
153
153
|
* 唯一 scope 总数: 74
|
|
154
154
|
* 必需应用权限总数: 20
|
|
155
155
|
* 高敏感权限总数: 4
|
|
156
|
-
*/
|
|
156
|
+
*/
|
package/src/core/tool-scopes.js
CHANGED
|
@@ -107,10 +107,13 @@ exports.TOOL_SCOPES = {
|
|
|
107
107
|
'feishu_calendar_event_attendee.create': ['calendar:calendar.event:update'],
|
|
108
108
|
'feishu_calendar_event_attendee.list': ['calendar:calendar.event:read'],
|
|
109
109
|
'feishu_calendar_freebusy.list': ['calendar:calendar.free_busy:read'],
|
|
110
|
+
'feishu_task_attachment.upload': ['task:attachment:write'],
|
|
110
111
|
'feishu_task_task.create': ['task:task:write', 'task:task:writeonly'],
|
|
111
112
|
'feishu_task_task.get': ['task:task:read', 'task:task:write'],
|
|
112
113
|
'feishu_task_task.list': ['task:task:read', 'task:task:write'],
|
|
113
114
|
'feishu_task_task.patch': ['task:task:write', 'task:task:writeonly'],
|
|
115
|
+
'feishu_task_task.add_members': ['task:task:write', 'task:task:writeonly'],
|
|
116
|
+
'feishu_task_task.append_steps': ['task:task:write', 'task:task:writeonly'],
|
|
114
117
|
'feishu_task_tasklist.create': ['task:tasklist:write'],
|
|
115
118
|
'feishu_task_tasklist.get': ['task:tasklist:read', 'task:tasklist:write'],
|
|
116
119
|
'feishu_task_tasklist.list': ['task:tasklist:read', 'task:tasklist:write'],
|
|
@@ -127,6 +130,8 @@ exports.TOOL_SCOPES = {
|
|
|
127
130
|
'feishu_task_section.tasks': ['task:task'],
|
|
128
131
|
'feishu_task_subtask.create': ['task:task:write'],
|
|
129
132
|
'feishu_task_subtask.list': ['task:task:read', 'task:task:write'],
|
|
133
|
+
'feishu_task_agent.register': ['task:task:write'],
|
|
134
|
+
'feishu_task_agent.update_profile': ['task:task:write', 'task:task:writeonly'],
|
|
130
135
|
'feishu_chat.search': ['im:chat:read'],
|
|
131
136
|
'feishu_chat.get': ['im:chat:read'],
|
|
132
137
|
'feishu_chat_members.default': ['im:chat.members:read'],
|
|
@@ -336,4 +341,4 @@ function filterSensitiveScopes(scopes) {
|
|
|
336
341
|
* 唯一 scope 总数: 74
|
|
337
342
|
* 必需应用权限总数: 20
|
|
338
343
|
* 高敏感权限总数: 4
|
|
339
|
-
*/
|
|
344
|
+
*/
|
|
@@ -22,6 +22,8 @@ export declare function dispatchToAgent(params: {
|
|
|
22
22
|
ctx: MessageContext;
|
|
23
23
|
permissionError?: PermissionError;
|
|
24
24
|
mediaPayload: Record<string, unknown>;
|
|
25
|
+
/** Additional structured metadata for synthetic or event-driven inbound flows. */
|
|
26
|
+
extraInboundFields?: Record<string, unknown>;
|
|
25
27
|
quotedContent?: string;
|
|
26
28
|
account: LarkAccount;
|
|
27
29
|
/** account 级别的 ClawdbotConfig(channels.feishu 已替换为 per-account 合并后的配置) */
|