@larksuite/openclaw-lark 2026.4.1 → 2026.4.7
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/index.js +25 -6
- package/package.json +1 -1
- package/src/card/builder.d.ts +29 -7
- package/src/card/builder.js +241 -29
- package/src/card/reasoning-utils.d.ts +14 -0
- package/src/card/reasoning-utils.js +64 -0
- package/src/card/reply-dispatcher-types.d.ts +12 -15
- package/src/card/reply-dispatcher-types.js +1 -0
- package/src/card/reply-dispatcher.js +63 -11
- package/src/card/streaming-card-controller.d.ts +15 -0
- package/src/card/streaming-card-controller.js +188 -65
- package/src/card/tool-use-config.d.ts +26 -0
- package/src/card/tool-use-config.js +76 -0
- package/src/card/tool-use-display.d.ts +29 -0
- package/src/card/tool-use-display.js +438 -0
- package/src/card/tool-use-trace-store.d.ts +51 -0
- package/src/card/tool-use-trace-store.js +271 -0
- package/src/channel/event-handlers.d.ts +1 -0
- package/src/channel/event-handlers.js +51 -0
- package/src/channel/monitor.js +2 -0
- package/src/core/comment-target.d.ts +65 -0
- package/src/core/comment-target.js +100 -0
- package/src/core/config-schema.d.ts +9 -0
- package/src/core/config-schema.js +5 -0
- package/src/core/tool-scopes.d.ts +1 -1
- package/src/core/tool-scopes.js +7 -0
- package/src/messaging/inbound/comment-context.d.ts +82 -0
- package/src/messaging/inbound/comment-context.js +353 -0
- package/src/messaging/inbound/comment-handler.d.ts +30 -0
- package/src/messaging/inbound/comment-handler.js +269 -0
- package/src/messaging/inbound/dispatch-commands.js +23 -2
- package/src/messaging/inbound/dispatch-context.js +19 -7
- package/src/messaging/inbound/dispatch.js +87 -8
- package/src/messaging/outbound/deliver.d.ts +29 -0
- package/src/messaging/outbound/deliver.js +94 -0
- package/src/messaging/outbound/outbound.js +19 -0
- package/src/messaging/types.d.ts +63 -0
- package/src/tools/oapi/drive/doc-comments.js +93 -24
- package/src/tools/oapi/index.js +1 -0
- package/src/tools/oapi/task/index.d.ts +1 -0
- package/src/tools/oapi/task/index.js +3 -1
- package/src/tools/oapi/task/section.d.ts +17 -0
- package/src/tools/oapi/task/section.js +285 -0
|
@@ -4,9 +4,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.sendTextLark = sendTextLark;
|
|
5
5
|
exports.sendCardLark = sendCardLark;
|
|
6
6
|
exports.sendMediaLark = sendMediaLark;
|
|
7
|
+
exports.sendCommentReplyLark = sendCommentReplyLark;
|
|
7
8
|
const accounts_1 = require("../../core/accounts.js");
|
|
8
9
|
const lark_client_1 = require("../../core/lark-client.js");
|
|
9
10
|
const targets_1 = require("../../core/targets.js");
|
|
11
|
+
const comment_target_1 = require("../../core/comment-target.js");
|
|
10
12
|
const markdown_style_1 = require("../../card/markdown-style.js");
|
|
11
13
|
const api_error_1 = require("../../core/api-error.js");
|
|
12
14
|
const lark_logger_1 = require("../../core/lark-logger.js");
|
|
@@ -307,3 +309,95 @@ async function sendMediaLark(params) {
|
|
|
307
309
|
};
|
|
308
310
|
}
|
|
309
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Send a text message to a Feishu Drive comment surface.
|
|
314
|
+
*
|
|
315
|
+
* Parses the comment target from `to`, then chooses one of two delivery
|
|
316
|
+
* strategies:
|
|
317
|
+
* - `reply` → reply in the existing comment thread
|
|
318
|
+
* - `create_whole` → create a new whole-document comment
|
|
319
|
+
*
|
|
320
|
+
* Returns a synthetic FeishuSendResult (no IM messageId).
|
|
321
|
+
*
|
|
322
|
+
* @throws {Error} When the target is not a valid comment target or API fails.
|
|
323
|
+
*/
|
|
324
|
+
async function sendCommentReplyLark(params) {
|
|
325
|
+
const { cfg, to, text, accountId } = params;
|
|
326
|
+
const target = (0, comment_target_1.parseFeishuCommentTarget)(to);
|
|
327
|
+
if (!target) {
|
|
328
|
+
throw new Error(`Not a valid comment target: "${to}"`);
|
|
329
|
+
}
|
|
330
|
+
log.info(`sendCommentReplyLark: mode=${target.deliveryMode}, comment=${target.commentId}, textLength=${text.length}`);
|
|
331
|
+
const client = lark_client_1.LarkClient.fromCfg(cfg, accountId);
|
|
332
|
+
const elements = [{ type: 'text_run', text_run: { text } }];
|
|
333
|
+
if (target.deliveryMode === 'create_whole') {
|
|
334
|
+
try {
|
|
335
|
+
await client.sdk.drive.v1.fileComment.create({
|
|
336
|
+
path: { file_token: target.fileToken },
|
|
337
|
+
params: {
|
|
338
|
+
file_type: target.fileType,
|
|
339
|
+
user_id_type: 'open_id',
|
|
340
|
+
},
|
|
341
|
+
data: {
|
|
342
|
+
reply_list: {
|
|
343
|
+
replies: [
|
|
344
|
+
{
|
|
345
|
+
content: {
|
|
346
|
+
elements,
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
log.info(`whole comment created successfully`);
|
|
354
|
+
return {
|
|
355
|
+
messageId: `comment-create:${target.commentId}`,
|
|
356
|
+
chatId: to,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
const detail = (0, api_error_1.formatLarkError)(err);
|
|
361
|
+
log.error(`sendCommentReplyLark failed to create whole comment: ${detail}`);
|
|
362
|
+
throw new Error(`Comment create failed: ${detail}`, { cause: err });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// sdk.request defaults to tenant_access_token (bot identity).
|
|
366
|
+
// Dual payload format: try content.elements first, fallback to reply_elements.
|
|
367
|
+
const url = `/open-apis/drive/v1/files/${target.fileToken}/comments/${target.commentId}/replies`;
|
|
368
|
+
const queryParams = { file_type: target.fileType, user_id_type: 'open_id' };
|
|
369
|
+
try {
|
|
370
|
+
await client.sdk.request({
|
|
371
|
+
method: 'POST',
|
|
372
|
+
url,
|
|
373
|
+
params: queryParams,
|
|
374
|
+
data: { content: { elements } },
|
|
375
|
+
});
|
|
376
|
+
log.info(`comment reply sent successfully`);
|
|
377
|
+
return {
|
|
378
|
+
messageId: `comment-reply:${target.commentId}`,
|
|
379
|
+
chatId: to,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
catch (firstErr) {
|
|
383
|
+
// Fallback: some API versions use reply_elements format
|
|
384
|
+
try {
|
|
385
|
+
await client.sdk.request({
|
|
386
|
+
method: 'POST',
|
|
387
|
+
url,
|
|
388
|
+
params: queryParams,
|
|
389
|
+
data: { reply_elements: elements },
|
|
390
|
+
});
|
|
391
|
+
log.info(`comment reply sent successfully (reply_elements fallback)`);
|
|
392
|
+
return {
|
|
393
|
+
messageId: `comment-reply:${target.commentId}`,
|
|
394
|
+
chatId: to,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
catch (secondErr) {
|
|
398
|
+
const detail = (0, api_error_1.formatLarkError)(firstErr);
|
|
399
|
+
log.error(`sendCommentReplyLark failed: ${detail}`);
|
|
400
|
+
throw new Error(`Comment reply failed: ${detail}`, { cause: secondErr });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
@@ -5,6 +5,7 @@ exports.feishuOutbound = void 0;
|
|
|
5
5
|
const lark_client_1 = require("../../core/lark-client.js");
|
|
6
6
|
const lark_logger_1 = require("../../core/lark-logger.js");
|
|
7
7
|
const targets_1 = require("../../core/targets.js");
|
|
8
|
+
const comment_target_1 = require("../../core/comment-target.js");
|
|
8
9
|
const deliver_1 = require("./deliver.js");
|
|
9
10
|
const log = (0, lark_logger_1.larkLogger)('outbound/outbound');
|
|
10
11
|
/**
|
|
@@ -40,12 +41,30 @@ exports.feishuOutbound = {
|
|
|
40
41
|
textChunkLimit: 15000,
|
|
41
42
|
sendText: async ({ cfg, to, text, accountId, replyToId, threadId }) => {
|
|
42
43
|
log.info(`sendText: target=${to}, textLength=${text.length}`);
|
|
44
|
+
// Comment thread routing — route replies through Drive comment API
|
|
45
|
+
if ((0, comment_target_1.isCommentTarget)(to)) {
|
|
46
|
+
log.info(`sendText: detected comment target, routing through Drive comment API`);
|
|
47
|
+
const result = await (0, deliver_1.sendCommentReplyLark)({ cfg, to, text, accountId: accountId ?? undefined });
|
|
48
|
+
return { channel: 'feishu', ...result };
|
|
49
|
+
}
|
|
43
50
|
const ctx = resolveFeishuSendContext({ cfg, to, accountId, replyToId, threadId });
|
|
44
51
|
const result = await (0, deliver_1.sendTextLark)({ ...ctx, to: ctx.to, text });
|
|
45
52
|
return { channel: 'feishu', ...result };
|
|
46
53
|
},
|
|
47
54
|
sendMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, accountId, replyToId, threadId }) => {
|
|
48
55
|
log.info(`sendMedia: target=${to}, ` + `hasText=${Boolean(text?.trim())}, mediaUrl=${mediaUrl ?? '(none)'}`);
|
|
56
|
+
// Comment thread routing — send text (with media URL appended) via Drive comment API
|
|
57
|
+
if ((0, comment_target_1.isCommentTarget)(to)) {
|
|
58
|
+
log.info(`sendMedia: detected comment target, routing through Drive comment API`);
|
|
59
|
+
const parts = [];
|
|
60
|
+
if (text?.trim())
|
|
61
|
+
parts.push(text.trim());
|
|
62
|
+
if (mediaUrl)
|
|
63
|
+
parts.push(`📎 ${mediaUrl}`);
|
|
64
|
+
const combinedText = parts.join('\n') || '(media)';
|
|
65
|
+
const result = await (0, deliver_1.sendCommentReplyLark)({ cfg, to, text: combinedText, accountId: accountId ?? undefined });
|
|
66
|
+
return { channel: 'feishu', ...result };
|
|
67
|
+
}
|
|
49
68
|
const ctx = resolveFeishuSendContext({ cfg, to, accountId, replyToId, threadId });
|
|
50
69
|
// Feishu media messages do not support inline captions — send text first.
|
|
51
70
|
if (text?.trim()) {
|
package/src/messaging/types.d.ts
CHANGED
|
@@ -56,6 +56,69 @@ export interface FeishuReactionCreatedEvent {
|
|
|
56
56
|
};
|
|
57
57
|
action_time?: string;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Raw event shape for `drive.notice.comment_add_v1`.
|
|
61
|
+
*
|
|
62
|
+
* Fired when a user adds a comment or reply on a Drive document.
|
|
63
|
+
* The SDK flattens the v2 envelope header into the handler `data` object,
|
|
64
|
+
* so `app_id` is available directly on the event.
|
|
65
|
+
*
|
|
66
|
+
* **Real event structure** (SDK-flattened):
|
|
67
|
+
* ```json
|
|
68
|
+
* {
|
|
69
|
+
* "app_id": "cli_xxx",
|
|
70
|
+
* "file_token": "xxx",
|
|
71
|
+
* "file_type": "docx",
|
|
72
|
+
* "comment_id": "xxx",
|
|
73
|
+
* "reply_id": "xxx", // optional, present for replies
|
|
74
|
+
* "notice_meta": {
|
|
75
|
+
* "from_user_id": { "open_id": "ou_xxx", "user_id": "xxx", "union_id": "xxx" },
|
|
76
|
+
* "timestamp": "1712000000000",
|
|
77
|
+
* "is_mentioned": true
|
|
78
|
+
* }
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* Some fields may also appear at top-level depending on SDK version,
|
|
83
|
+
* so the parser checks both locations.
|
|
84
|
+
*/
|
|
85
|
+
export interface FeishuDriveCommentEvent {
|
|
86
|
+
/** App ID from the event envelope header. */
|
|
87
|
+
app_id?: string;
|
|
88
|
+
/** File token of the document where the comment was added. */
|
|
89
|
+
file_token?: string;
|
|
90
|
+
/** File type: doc, docx, sheet, file, slides, etc. */
|
|
91
|
+
file_type?: string;
|
|
92
|
+
/** Comment ID of the root comment. */
|
|
93
|
+
comment_id?: string;
|
|
94
|
+
/** Reply ID (present when the event is a reply, not a root comment). */
|
|
95
|
+
reply_id?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Metadata about the notice — primary location for user info and timestamp.
|
|
98
|
+
* Present in the real event structure; not always at top level.
|
|
99
|
+
*/
|
|
100
|
+
notice_meta?: {
|
|
101
|
+
from_user_id?: {
|
|
102
|
+
open_id?: string;
|
|
103
|
+
user_id?: string;
|
|
104
|
+
union_id?: string;
|
|
105
|
+
};
|
|
106
|
+
file_token?: string;
|
|
107
|
+
file_type?: string;
|
|
108
|
+
timestamp?: string;
|
|
109
|
+
is_mentioned?: boolean;
|
|
110
|
+
};
|
|
111
|
+
/** Whether the bot was @-mentioned in this comment. */
|
|
112
|
+
is_mention?: boolean;
|
|
113
|
+
/** Fallback: user ID at top level (some SDK flattening styles). */
|
|
114
|
+
user_id?: {
|
|
115
|
+
open_id?: string;
|
|
116
|
+
user_id?: string;
|
|
117
|
+
union_id?: string;
|
|
118
|
+
};
|
|
119
|
+
/** Fallback: event timestamp at top level. */
|
|
120
|
+
action_time?: string;
|
|
121
|
+
}
|
|
59
122
|
export interface FeishuBotAddedEvent {
|
|
60
123
|
chat_id: string;
|
|
61
124
|
operator_id: {
|
|
@@ -27,7 +27,7 @@ const ReplyElementSchema = typebox_1.Type.Object({
|
|
|
27
27
|
url: typebox_1.Type.Optional(typebox_1.Type.String({ description: '链接URL(type=link时必填)' })),
|
|
28
28
|
});
|
|
29
29
|
const DocCommentsSchema = typebox_1.Type.Object({
|
|
30
|
-
action: (0, helpers_1.StringEnum)(['list', 'create', 'patch']),
|
|
30
|
+
action: (0, helpers_1.StringEnum)(['list', 'list_replies', 'create', 'reply', 'patch']),
|
|
31
31
|
file_token: typebox_1.Type.String({
|
|
32
32
|
description: '云文档token或wiki节点token(可从文档URL获取)。如果是wiki token,会自动转换为实际文档的obj_token',
|
|
33
33
|
}),
|
|
@@ -43,13 +43,13 @@ const DocCommentsSchema = typebox_1.Type.Object({
|
|
|
43
43
|
})),
|
|
44
44
|
page_size: typebox_1.Type.Optional(typebox_1.Type.Integer({ description: '分页大小' })),
|
|
45
45
|
page_token: typebox_1.Type.Optional(typebox_1.Type.String({ description: '分页标记' })),
|
|
46
|
-
// create action参数
|
|
46
|
+
// create / reply action参数
|
|
47
47
|
elements: typebox_1.Type.Optional(typebox_1.Type.Array(ReplyElementSchema, {
|
|
48
|
-
description: '评论内容元素数组(action=create时必填)。' + '支持text(纯文本)、mention(@用户)、link(超链接)三种类型',
|
|
48
|
+
description: '评论内容元素数组(action=create/reply时必填)。' + '支持text(纯文本)、mention(@用户)、link(超链接)三种类型',
|
|
49
49
|
})),
|
|
50
|
-
// patch action参数
|
|
50
|
+
// list_replies / reply / patch action参数
|
|
51
51
|
comment_id: typebox_1.Type.Optional(typebox_1.Type.String({
|
|
52
|
-
description: '评论ID(action=patch时必填)',
|
|
52
|
+
description: '评论ID(action=list_replies/reply/patch时必填)',
|
|
53
53
|
})),
|
|
54
54
|
is_solved_value: typebox_1.Type.Optional(typebox_1.Type.Boolean({
|
|
55
55
|
description: '解决状态:true=解决,false=恢复(action=patch时必填)',
|
|
@@ -109,7 +109,7 @@ async function assembleCommentsWithReplies(client, file_token, file_type, commen
|
|
|
109
109
|
page_size: 50,
|
|
110
110
|
user_id_type,
|
|
111
111
|
},
|
|
112
|
-
}, opts), { as: '
|
|
112
|
+
}, opts), { as: 'tenant' });
|
|
113
113
|
const replyData = replyRes.data;
|
|
114
114
|
if (replyRes.code === 0 && replyData?.items) {
|
|
115
115
|
replies.push(...(replyData.items || []));
|
|
@@ -145,8 +145,10 @@ function registerDocCommentsTool(api) {
|
|
|
145
145
|
label: 'Feishu: Doc Comments',
|
|
146
146
|
description: '【以用户身份】管理云文档评论。支持: ' +
|
|
147
147
|
'(1) list - 获取评论列表(含完整回复); ' +
|
|
148
|
-
'(2)
|
|
149
|
-
'(3)
|
|
148
|
+
'(2) list_replies - 获取指定评论的回复列表; ' +
|
|
149
|
+
'(3) create - 添加全文评论(支持文本、@用户、超链接); ' +
|
|
150
|
+
'(4) reply - 回复已有评论; ' +
|
|
151
|
+
'(5) patch - 解决/恢复评论。' +
|
|
150
152
|
'支持 wiki token。',
|
|
151
153
|
parameters: DocCommentsSchema,
|
|
152
154
|
async execute(_toolCallId, params) {
|
|
@@ -176,7 +178,7 @@ function registerDocCommentsTool(api) {
|
|
|
176
178
|
}
|
|
177
179
|
actualFileToken = node.obj_token;
|
|
178
180
|
actualFileType = node.obj_type;
|
|
179
|
-
log.info(`doc_comments: wiki token converted
|
|
181
|
+
log.info(`doc_comments: wiki token converted, obj_type="${actualFileType}"`);
|
|
180
182
|
}
|
|
181
183
|
catch (err) {
|
|
182
184
|
log.error(`doc_comments: failed to convert wiki token: ${err}`);
|
|
@@ -187,7 +189,7 @@ function registerDocCommentsTool(api) {
|
|
|
187
189
|
}
|
|
188
190
|
// Action: list - 获取评论列表
|
|
189
191
|
if (p.action === 'list') {
|
|
190
|
-
log.info(`doc_comments.list:
|
|
192
|
+
log.info(`doc_comments.list: file_type=${actualFileType}`);
|
|
191
193
|
const res = await client.invoke('feishu_doc_comments.list', (sdk, opts) => sdk.drive.v1.fileComment.list({
|
|
192
194
|
path: { file_token: actualFileToken },
|
|
193
195
|
params: {
|
|
@@ -198,7 +200,7 @@ function registerDocCommentsTool(api) {
|
|
|
198
200
|
page_token: p.page_token,
|
|
199
201
|
user_id_type: userIdType,
|
|
200
202
|
},
|
|
201
|
-
}, opts), { as: '
|
|
203
|
+
}, opts), { as: 'tenant' });
|
|
202
204
|
(0, helpers_1.assertLarkOk)(res);
|
|
203
205
|
const items = res.data?.items || [];
|
|
204
206
|
log.info(`doc_comments.list: found ${items.length} comments`);
|
|
@@ -210,6 +212,35 @@ function registerDocCommentsTool(api) {
|
|
|
210
212
|
page_token: res.data?.page_token,
|
|
211
213
|
});
|
|
212
214
|
}
|
|
215
|
+
// Action: list_replies - 获取指定评论的回复列表
|
|
216
|
+
if (p.action === 'list_replies') {
|
|
217
|
+
if (!p.comment_id) {
|
|
218
|
+
return (0, helpers_1.json)({ error: 'comment_id 参数必填' });
|
|
219
|
+
}
|
|
220
|
+
log.info(`doc_comments.list_replies: comment_id="${p.comment_id}"`);
|
|
221
|
+
// Single-page fetch — return items + pagination metadata,
|
|
222
|
+
// consistent with the list action's API semantics.
|
|
223
|
+
const replyRes = await client.invoke('feishu_doc_comments.list_replies', (sdk, opts) => sdk.drive.v1.fileCommentReply.list({
|
|
224
|
+
path: {
|
|
225
|
+
file_token: actualFileToken,
|
|
226
|
+
comment_id: p.comment_id,
|
|
227
|
+
},
|
|
228
|
+
params: {
|
|
229
|
+
file_type: actualFileType,
|
|
230
|
+
page_token: p.page_token,
|
|
231
|
+
page_size: p.page_size || 50,
|
|
232
|
+
user_id_type: userIdType,
|
|
233
|
+
},
|
|
234
|
+
}, opts), { as: 'tenant' });
|
|
235
|
+
(0, helpers_1.assertLarkOk)(replyRes);
|
|
236
|
+
const replyData = replyRes.data;
|
|
237
|
+
log.info(`doc_comments.list_replies: found ${replyData?.items?.length ?? 0} replies`);
|
|
238
|
+
return (0, helpers_1.json)({
|
|
239
|
+
items: replyData?.items ?? [],
|
|
240
|
+
has_more: replyData?.has_more ?? false,
|
|
241
|
+
page_token: replyData?.page_token,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
213
244
|
// Action: create - 创建评论
|
|
214
245
|
if (p.action === 'create') {
|
|
215
246
|
if (!p.elements || p.elements.length === 0) {
|
|
@@ -217,30 +248,68 @@ function registerDocCommentsTool(api) {
|
|
|
217
248
|
error: 'elements 参数必填且不能为空',
|
|
218
249
|
});
|
|
219
250
|
}
|
|
220
|
-
log.info(`doc_comments.create:
|
|
251
|
+
log.info(`doc_comments.create: file_type=${actualFileType}, elements=${p.elements.length}`);
|
|
221
252
|
const sdkElements = convertElementsToSDKFormat(p.elements);
|
|
253
|
+
const commentData = {
|
|
254
|
+
reply_list: {
|
|
255
|
+
replies: [
|
|
256
|
+
{
|
|
257
|
+
content: {
|
|
258
|
+
elements: sdkElements,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
};
|
|
222
264
|
const res = await client.invoke('feishu_doc_comments.create', (sdk, opts) => sdk.drive.v1.fileComment.create({
|
|
223
265
|
path: { file_token: actualFileToken },
|
|
224
266
|
params: {
|
|
225
267
|
file_type: actualFileType,
|
|
226
268
|
user_id_type: userIdType,
|
|
227
269
|
},
|
|
228
|
-
data:
|
|
229
|
-
|
|
230
|
-
replies: [
|
|
231
|
-
{
|
|
232
|
-
content: {
|
|
233
|
-
elements: sdkElements,
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
],
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
}, opts), { as: 'user' });
|
|
270
|
+
data: commentData,
|
|
271
|
+
}, opts), { as: 'tenant' });
|
|
240
272
|
(0, helpers_1.assertLarkOk)(res);
|
|
241
273
|
log.info(`doc_comments.create: created comment ${res.data?.comment_id}`);
|
|
242
274
|
return (0, helpers_1.json)(res.data);
|
|
243
275
|
}
|
|
276
|
+
// Action: reply - 回复已有评论
|
|
277
|
+
if (p.action === 'reply') {
|
|
278
|
+
if (!p.comment_id) {
|
|
279
|
+
return (0, helpers_1.json)({ error: 'comment_id 参数必填' });
|
|
280
|
+
}
|
|
281
|
+
if (!p.elements || p.elements.length === 0) {
|
|
282
|
+
return (0, helpers_1.json)({ error: 'elements 参数必填且不能为空' });
|
|
283
|
+
}
|
|
284
|
+
log.info(`doc_comments.reply: comment_id="${p.comment_id}", elements=${p.elements.length}`);
|
|
285
|
+
const sdkElements = convertElementsToSDKFormat(p.elements);
|
|
286
|
+
// 使用 sdk.request() 发起请求(与 deliver.ts 一致),SDK 内部自动管理 TAT。
|
|
287
|
+
// 双重 payload 格式兜底:先试 content.elements,失败再试 reply_elements。
|
|
288
|
+
const replyUrl = `/open-apis/drive/v1/files/${actualFileToken}/comments/${p.comment_id}/replies`;
|
|
289
|
+
const replyParams = { file_type: actualFileType, user_id_type: userIdType };
|
|
290
|
+
let res;
|
|
291
|
+
try {
|
|
292
|
+
res = await client.invoke('feishu_doc_comments.reply', (sdk) => sdk.request({
|
|
293
|
+
method: 'POST',
|
|
294
|
+
url: replyUrl,
|
|
295
|
+
params: replyParams,
|
|
296
|
+
data: { content: { elements: sdkElements } },
|
|
297
|
+
}), { as: 'tenant' });
|
|
298
|
+
}
|
|
299
|
+
catch (_firstErr) {
|
|
300
|
+
// Fallback: 部分 API 版本使用 reply_elements 格式
|
|
301
|
+
log.info(`doc_comments.reply: first attempt failed, trying reply_elements format`);
|
|
302
|
+
res = await client.invoke('feishu_doc_comments.reply', (sdk) => sdk.request({
|
|
303
|
+
method: 'POST',
|
|
304
|
+
url: replyUrl,
|
|
305
|
+
params: replyParams,
|
|
306
|
+
data: { reply_elements: sdkElements },
|
|
307
|
+
}), { as: 'tenant' });
|
|
308
|
+
}
|
|
309
|
+
(0, helpers_1.assertLarkOk)(res);
|
|
310
|
+
log.info(`doc_comments.reply: created reply`);
|
|
311
|
+
return (0, helpers_1.json)(res.data ?? res);
|
|
312
|
+
}
|
|
244
313
|
// Action: patch - 解决/恢复评论
|
|
245
314
|
if (p.action === 'patch') {
|
|
246
315
|
if (!p.comment_id) {
|
package/src/tools/oapi/index.js
CHANGED
|
@@ -39,6 +39,7 @@ function registerOapiTools(api) {
|
|
|
39
39
|
// Task tools
|
|
40
40
|
(0, index_3.registerFeishuTaskTaskTool)(api);
|
|
41
41
|
(0, index_3.registerFeishuTaskTasklistTool)(api);
|
|
42
|
+
(0, index_3.registerFeishuTaskSectionTool)(api);
|
|
42
43
|
(0, index_3.registerFeishuTaskCommentTool)(api);
|
|
43
44
|
(0, index_3.registerFeishuTaskSubtaskTool)(api);
|
|
44
45
|
// Bitable tools
|
|
@@ -6,3 +6,4 @@ export { registerFeishuTaskTaskTool } from './task';
|
|
|
6
6
|
export { registerFeishuTaskTasklistTool } from './tasklist';
|
|
7
7
|
export { registerFeishuTaskCommentTool } from './comment';
|
|
8
8
|
export { registerFeishuTaskSubtaskTool } from './subtask';
|
|
9
|
+
export { registerFeishuTaskSectionTool } from './section';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.registerFeishuTaskSubtaskTool = exports.registerFeishuTaskCommentTool = exports.registerFeishuTaskTasklistTool = exports.registerFeishuTaskTaskTool = void 0;
|
|
7
|
+
exports.registerFeishuTaskSectionTool = exports.registerFeishuTaskSubtaskTool = exports.registerFeishuTaskCommentTool = exports.registerFeishuTaskTasklistTool = exports.registerFeishuTaskTaskTool = void 0;
|
|
8
8
|
var task_1 = require("./task.js");
|
|
9
9
|
Object.defineProperty(exports, "registerFeishuTaskTaskTool", { enumerable: true, get: function () { return task_1.registerFeishuTaskTaskTool; } });
|
|
10
10
|
var tasklist_1 = require("./tasklist.js");
|
|
@@ -13,3 +13,5 @@ var comment_1 = require("./comment.js");
|
|
|
13
13
|
Object.defineProperty(exports, "registerFeishuTaskCommentTool", { enumerable: true, get: function () { return comment_1.registerFeishuTaskCommentTool; } });
|
|
14
14
|
var subtask_1 = require("./subtask.js");
|
|
15
15
|
Object.defineProperty(exports, "registerFeishuTaskSubtaskTool", { enumerable: true, get: function () { return subtask_1.registerFeishuTaskSubtaskTool; } });
|
|
16
|
+
var section_1 = require("./section.js");
|
|
17
|
+
Object.defineProperty(exports, "registerFeishuTaskSectionTool", { enumerable: true, get: function () { return section_1.registerFeishuTaskSectionTool; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* feishu_task_section tool -- Manage Feishu task sections.
|
|
6
|
+
*
|
|
7
|
+
* P0 Actions: create, get, list, patch, tasks
|
|
8
|
+
*
|
|
9
|
+
* Uses the Feishu Task v2 API:
|
|
10
|
+
* - create: POST /open-apis/task/v2/sections
|
|
11
|
+
* - get: GET /open-apis/task/v2/sections/:section_guid
|
|
12
|
+
* - patch: PATCH /open-apis/task/v2/sections/:section_guid
|
|
13
|
+
* - list: GET /open-apis/task/v2/sections
|
|
14
|
+
* - tasks: GET /open-apis/task/v2/sections/:section_guid/tasks
|
|
15
|
+
*/
|
|
16
|
+
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
17
|
+
export declare function registerFeishuTaskSectionTool(api: OpenClawPluginApi): void;
|