@bobotu/feishu-fork 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +922 -0
  3. package/index.ts +65 -0
  4. package/openclaw.plugin.json +10 -0
  5. package/package.json +72 -0
  6. package/skills/feishu-doc/SKILL.md +161 -0
  7. package/skills/feishu-doc/references/block-types.md +102 -0
  8. package/skills/feishu-drive/SKILL.md +96 -0
  9. package/skills/feishu-perm/SKILL.md +90 -0
  10. package/skills/feishu-task/SKILL.md +210 -0
  11. package/skills/feishu-wiki/SKILL.md +96 -0
  12. package/src/accounts.ts +140 -0
  13. package/src/bitable-tools/actions.ts +199 -0
  14. package/src/bitable-tools/common.ts +90 -0
  15. package/src/bitable-tools/index.ts +1 -0
  16. package/src/bitable-tools/meta.ts +80 -0
  17. package/src/bitable-tools/register.ts +195 -0
  18. package/src/bitable-tools/schemas.ts +221 -0
  19. package/src/bot.ts +1125 -0
  20. package/src/channel.ts +334 -0
  21. package/src/client.ts +114 -0
  22. package/src/config-schema.ts +237 -0
  23. package/src/dedup.ts +54 -0
  24. package/src/directory.ts +165 -0
  25. package/src/doc-tools/actions.ts +341 -0
  26. package/src/doc-tools/common.ts +33 -0
  27. package/src/doc-tools/index.ts +2 -0
  28. package/src/doc-tools/register.ts +90 -0
  29. package/src/doc-tools/schemas.ts +85 -0
  30. package/src/doc-write-service.ts +711 -0
  31. package/src/drive-tools/actions.ts +182 -0
  32. package/src/drive-tools/common.ts +18 -0
  33. package/src/drive-tools/index.ts +2 -0
  34. package/src/drive-tools/register.ts +71 -0
  35. package/src/drive-tools/schemas.ts +67 -0
  36. package/src/dynamic-agent.ts +135 -0
  37. package/src/external-keys.ts +19 -0
  38. package/src/media.ts +510 -0
  39. package/src/mention.ts +121 -0
  40. package/src/monitor.ts +323 -0
  41. package/src/onboarding.ts +449 -0
  42. package/src/outbound.ts +40 -0
  43. package/src/perm-tools/actions.ts +111 -0
  44. package/src/perm-tools/common.ts +18 -0
  45. package/src/perm-tools/index.ts +2 -0
  46. package/src/perm-tools/register.ts +65 -0
  47. package/src/perm-tools/schemas.ts +52 -0
  48. package/src/policy.ts +117 -0
  49. package/src/probe.ts +147 -0
  50. package/src/reactions.ts +160 -0
  51. package/src/reply-dispatcher.ts +240 -0
  52. package/src/runtime.ts +14 -0
  53. package/src/send.ts +391 -0
  54. package/src/streaming-card.ts +211 -0
  55. package/src/targets.ts +58 -0
  56. package/src/task-tools/actions.ts +590 -0
  57. package/src/task-tools/common.ts +18 -0
  58. package/src/task-tools/constants.ts +13 -0
  59. package/src/task-tools/index.ts +1 -0
  60. package/src/task-tools/register.ts +263 -0
  61. package/src/task-tools/schemas.ts +567 -0
  62. package/src/text/markdown-links.ts +104 -0
  63. package/src/tools-common/feishu-api.ts +184 -0
  64. package/src/tools-common/tool-context.ts +23 -0
  65. package/src/tools-common/tool-exec.ts +73 -0
  66. package/src/tools-config.ts +22 -0
  67. package/src/types.ts +79 -0
  68. package/src/typing.ts +75 -0
  69. package/src/wiki-tools/actions.ts +166 -0
  70. package/src/wiki-tools/common.ts +18 -0
  71. package/src/wiki-tools/index.ts +2 -0
  72. package/src/wiki-tools/register.ts +66 -0
  73. package/src/wiki-tools/schemas.ts +55 -0
@@ -0,0 +1,567 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { TaskClient } from "./common.js";
3
+
4
+ type TaskCreatePayload = NonNullable<Parameters<TaskClient["task"]["v2"]["task"]["create"]>[0]>;
5
+ type TaskUpdatePayload = NonNullable<Parameters<TaskClient["task"]["v2"]["task"]["patch"]>[0]>;
6
+ type TaskDeletePayload = NonNullable<Parameters<TaskClient["task"]["v2"]["task"]["delete"]>[0]>;
7
+ type TaskGetPayload = NonNullable<Parameters<TaskClient["task"]["v2"]["task"]["get"]>[0]>;
8
+ type TaskAddTasklistPayload = NonNullable<
9
+ Parameters<TaskClient["task"]["v2"]["task"]["addTasklist"]>[0]
10
+ >;
11
+ type TaskRemoveTasklistPayload = NonNullable<
12
+ Parameters<TaskClient["task"]["v2"]["task"]["removeTasklist"]>[0]
13
+ >;
14
+ type TaskCommentCreatePayload = NonNullable<
15
+ Parameters<TaskClient["task"]["v2"]["comment"]["create"]>[0]
16
+ >;
17
+ type TaskCommentGetPayload = NonNullable<Parameters<TaskClient["task"]["v2"]["comment"]["get"]>[0]>;
18
+ type TaskCommentListPayload = NonNullable<
19
+ Parameters<TaskClient["task"]["v2"]["comment"]["list"]>[0]
20
+ >;
21
+ type TaskCommentPatchPayload = NonNullable<
22
+ Parameters<TaskClient["task"]["v2"]["comment"]["patch"]>[0]
23
+ >;
24
+ type TaskCommentDeletePayload = NonNullable<
25
+ Parameters<TaskClient["task"]["v2"]["comment"]["delete"]>[0]
26
+ >;
27
+ type TaskAttachmentUploadPayload = NonNullable<
28
+ Parameters<TaskClient["task"]["v2"]["attachment"]["upload"]>[0]
29
+ >;
30
+ type TaskAttachmentGetPayload = NonNullable<
31
+ Parameters<TaskClient["task"]["v2"]["attachment"]["get"]>[0]
32
+ >;
33
+ type TaskAttachmentListPayload = NonNullable<
34
+ Parameters<TaskClient["task"]["v2"]["attachment"]["list"]>[0]
35
+ >;
36
+ type TaskAttachmentDeletePayload = NonNullable<
37
+ Parameters<TaskClient["task"]["v2"]["attachment"]["delete"]>[0]
38
+ >;
39
+ type TasklistCreatePayload = NonNullable<
40
+ Parameters<TaskClient["task"]["v2"]["tasklist"]["create"]>[0]
41
+ >;
42
+ type TasklistGetPayload = NonNullable<Parameters<TaskClient["task"]["v2"]["tasklist"]["get"]>[0]>;
43
+ type TasklistListPayload = NonNullable<Parameters<TaskClient["task"]["v2"]["tasklist"]["list"]>[0]>;
44
+ type TasklistPatchPayload = NonNullable<
45
+ Parameters<TaskClient["task"]["v2"]["tasklist"]["patch"]>[0]
46
+ >;
47
+ type TasklistDeletePayload = NonNullable<
48
+ Parameters<TaskClient["task"]["v2"]["tasklist"]["delete"]>[0]
49
+ >;
50
+ type TasklistAddMembersPayload = NonNullable<
51
+ Parameters<TaskClient["task"]["v2"]["tasklist"]["addMembers"]>[0]
52
+ >;
53
+ type TasklistRemoveMembersPayload = NonNullable<
54
+ Parameters<TaskClient["task"]["v2"]["tasklist"]["removeMembers"]>[0]
55
+ >;
56
+
57
+ export type TaskCreateData = TaskCreatePayload["data"];
58
+ export type TaskUpdateData = TaskUpdatePayload["data"];
59
+ export type TaskUpdateTask = NonNullable<TaskUpdateData["task"]>;
60
+ export type TaskCommentPatchData = TaskCommentPatchPayload["data"];
61
+ export type TaskCommentPatchComment = TaskCommentPatchData["comment"];
62
+ export type TasklistCreateData = TasklistCreatePayload["data"];
63
+ export type TasklistPatchData = TasklistPatchPayload["data"];
64
+ export type TasklistPatchTasklist = NonNullable<TasklistPatchData["tasklist"]>;
65
+ export type TasklistMember = NonNullable<NonNullable<TasklistCreateData["members"]>[number]>;
66
+
67
+ export const TASK_UPDATE_FIELD_VALUES = [
68
+ "summary",
69
+ "description",
70
+ "due",
71
+ "start",
72
+ "extra",
73
+ "completed_at",
74
+ "repeat_rule",
75
+ "mode",
76
+ "is_milestone",
77
+ ] as const;
78
+ export const TASKLIST_UPDATE_FIELD_VALUES = ["name", "owner", "archive_tasklist"] as const;
79
+
80
+ export type CreateTaskParams = {
81
+ summary: TaskCreateData["summary"];
82
+ description?: TaskCreateData["description"];
83
+ due?: TaskCreateData["due"];
84
+ start?: TaskCreateData["start"];
85
+ extra?: TaskCreateData["extra"];
86
+ completed_at?: TaskCreateData["completed_at"];
87
+ members?: TaskCreateData["members"];
88
+ repeat_rule?: TaskCreateData["repeat_rule"];
89
+ tasklists?: TaskCreateData["tasklists"];
90
+ mode?: TaskCreateData["mode"];
91
+ is_milestone?: TaskCreateData["is_milestone"];
92
+ user_id_type?: NonNullable<TaskCreatePayload["params"]>["user_id_type"];
93
+ };
94
+
95
+ export type CreateSubtaskParams = CreateTaskParams & {
96
+ task_guid: string;
97
+ };
98
+
99
+ export type DeleteTaskParams = {
100
+ task_guid: TaskDeletePayload["path"]["task_guid"];
101
+ };
102
+
103
+ export type GetTaskParams = {
104
+ task_guid: TaskGetPayload["path"]["task_guid"];
105
+ user_id_type?: NonNullable<TaskGetPayload["params"]>["user_id_type"];
106
+ };
107
+
108
+ export type UpdateTaskParams = {
109
+ task_guid: TaskUpdatePayload["path"]["task_guid"];
110
+ task: TaskUpdateTask;
111
+ update_fields?: TaskUpdateData["update_fields"];
112
+ user_id_type?: NonNullable<TaskUpdatePayload["params"]>["user_id_type"];
113
+ };
114
+
115
+ export type AddTaskToTasklistParams = {
116
+ task_guid: TaskAddTasklistPayload["path"]["task_guid"];
117
+ tasklist_guid: NonNullable<TaskAddTasklistPayload["data"]>["tasklist_guid"];
118
+ section_guid?: NonNullable<TaskAddTasklistPayload["data"]>["section_guid"];
119
+ user_id_type?: NonNullable<TaskAddTasklistPayload["params"]>["user_id_type"];
120
+ };
121
+
122
+ export type RemoveTaskFromTasklistParams = {
123
+ task_guid: TaskRemoveTasklistPayload["path"]["task_guid"];
124
+ tasklist_guid: NonNullable<TaskRemoveTasklistPayload["data"]>["tasklist_guid"];
125
+ user_id_type?: NonNullable<TaskRemoveTasklistPayload["params"]>["user_id_type"];
126
+ };
127
+
128
+ export type CreateTaskCommentParams = {
129
+ task_guid: string;
130
+ content: TaskCommentCreatePayload["data"]["content"];
131
+ reply_to_comment_id?: TaskCommentCreatePayload["data"]["reply_to_comment_id"];
132
+ user_id_type?: NonNullable<TaskCommentCreatePayload["params"]>["user_id_type"];
133
+ };
134
+
135
+ export type ListTaskCommentsParams = {
136
+ task_guid: string;
137
+ page_size?: NonNullable<TaskCommentListPayload["params"]>["page_size"];
138
+ page_token?: NonNullable<TaskCommentListPayload["params"]>["page_token"];
139
+ direction?: NonNullable<TaskCommentListPayload["params"]>["direction"];
140
+ user_id_type?: NonNullable<TaskCommentListPayload["params"]>["user_id_type"];
141
+ };
142
+
143
+ export type GetTaskCommentParams = {
144
+ comment_id: TaskCommentGetPayload["path"]["comment_id"];
145
+ user_id_type?: NonNullable<TaskCommentGetPayload["params"]>["user_id_type"];
146
+ };
147
+
148
+ export type UpdateTaskCommentParams = {
149
+ comment_id: TaskCommentPatchPayload["path"]["comment_id"];
150
+ comment: TaskCommentPatchComment;
151
+ update_fields?: TaskCommentPatchData["update_fields"];
152
+ user_id_type?: NonNullable<TaskCommentPatchPayload["params"]>["user_id_type"];
153
+ };
154
+
155
+ export type DeleteTaskCommentParams = {
156
+ comment_id: TaskCommentDeletePayload["path"]["comment_id"];
157
+ };
158
+
159
+ export type UploadTaskAttachmentParams =
160
+ | {
161
+ task_guid: string;
162
+ file_path: string;
163
+ user_id_type?: NonNullable<TaskAttachmentUploadPayload["params"]>["user_id_type"];
164
+ }
165
+ | {
166
+ task_guid: string;
167
+ file_url: string;
168
+ filename?: string;
169
+ user_id_type?: NonNullable<TaskAttachmentUploadPayload["params"]>["user_id_type"];
170
+ };
171
+
172
+ export type ListTaskAttachmentsParams = {
173
+ task_guid: NonNullable<TaskAttachmentListPayload["params"]>["resource_id"];
174
+ page_size?: NonNullable<TaskAttachmentListPayload["params"]>["page_size"];
175
+ page_token?: NonNullable<TaskAttachmentListPayload["params"]>["page_token"];
176
+ updated_mesc?: NonNullable<TaskAttachmentListPayload["params"]>["updated_mesc"];
177
+ user_id_type?: NonNullable<TaskAttachmentListPayload["params"]>["user_id_type"];
178
+ };
179
+
180
+ export type GetTaskAttachmentParams = {
181
+ attachment_guid: TaskAttachmentGetPayload["path"]["attachment_guid"];
182
+ user_id_type?: NonNullable<TaskAttachmentGetPayload["params"]>["user_id_type"];
183
+ };
184
+
185
+ export type DeleteTaskAttachmentParams = {
186
+ attachment_guid: NonNullable<TaskAttachmentDeletePayload["path"]>["attachment_guid"];
187
+ };
188
+
189
+ export type CreateTasklistParams = {
190
+ name: TasklistCreateData["name"];
191
+ members?: TasklistCreateData["members"];
192
+ archive_tasklist?: TasklistCreateData["archive_tasklist"];
193
+ user_id_type?: NonNullable<TasklistCreatePayload["params"]>["user_id_type"];
194
+ };
195
+
196
+ export type GetTasklistParams = {
197
+ tasklist_guid: NonNullable<TasklistGetPayload["path"]>["tasklist_guid"];
198
+ user_id_type?: NonNullable<TasklistGetPayload["params"]>["user_id_type"];
199
+ };
200
+
201
+ export type ListTasklistsParams = {
202
+ page_size?: NonNullable<TasklistListPayload["params"]>["page_size"];
203
+ page_token?: NonNullable<TasklistListPayload["params"]>["page_token"];
204
+ user_id_type?: NonNullable<TasklistListPayload["params"]>["user_id_type"];
205
+ };
206
+
207
+ export type UpdateTasklistParams = {
208
+ tasklist_guid: NonNullable<TasklistPatchPayload["path"]>["tasklist_guid"];
209
+ tasklist: TasklistPatchTasklist;
210
+ update_fields?: TasklistPatchData["update_fields"];
211
+ origin_owner_to_role?: TasklistPatchData["origin_owner_to_role"];
212
+ user_id_type?: NonNullable<TasklistPatchPayload["params"]>["user_id_type"];
213
+ };
214
+
215
+ export type DeleteTasklistParams = {
216
+ tasklist_guid: NonNullable<TasklistDeletePayload["path"]>["tasklist_guid"];
217
+ };
218
+
219
+ export type AddTasklistMembersParams = {
220
+ tasklist_guid: NonNullable<TasklistAddMembersPayload["path"]>["tasklist_guid"];
221
+ members: NonNullable<NonNullable<TasklistAddMembersPayload["data"]>["members"]>;
222
+ user_id_type?: NonNullable<TasklistAddMembersPayload["params"]>["user_id_type"];
223
+ };
224
+
225
+ export type RemoveTasklistMembersParams = {
226
+ tasklist_guid: NonNullable<TasklistRemoveMembersPayload["path"]>["tasklist_guid"];
227
+ members: NonNullable<NonNullable<TasklistRemoveMembersPayload["data"]>["members"]>;
228
+ user_id_type?: NonNullable<TasklistRemoveMembersPayload["params"]>["user_id_type"];
229
+ };
230
+
231
+ const TaskDateSchema = Type.Object({
232
+ timestamp: Type.Optional(
233
+ Type.String({
234
+ description:
235
+ 'Unix timestamp in milliseconds (string), e.g. "1735689600000" (13-digit ms)',
236
+ }),
237
+ ),
238
+ is_all_day: Type.Optional(Type.Boolean({ description: "Whether this is an all-day date" })),
239
+ });
240
+
241
+ const TaskMemberSchema = Type.Object({
242
+ id: Type.String({ description: "Member ID (with type controlled by user_id_type)" }),
243
+ type: Type.Optional(Type.String({ description: 'Member type (usually "user")' })),
244
+ role: Type.String({ description: 'Member role, e.g. "assignee"' }),
245
+ name: Type.Optional(Type.String({ description: "Optional display name" })),
246
+ });
247
+
248
+ const TasklistRefSchema = Type.Object({
249
+ tasklist_guid: Type.Optional(Type.String({ description: "Tasklist GUID" })),
250
+ section_guid: Type.Optional(Type.String({ description: "Section GUID in tasklist" })),
251
+ });
252
+
253
+ const TaskUpdateFieldSchema = Type.Union(
254
+ TASK_UPDATE_FIELD_VALUES.map((field) => Type.Literal(field)),
255
+ );
256
+
257
+ const TasklistMemberRoleSchema = Type.Union(
258
+ [Type.Literal("owner"), Type.Literal("editor"), Type.Literal("viewer")],
259
+ { description: "Member role (owner/editor/viewer)" },
260
+ );
261
+
262
+ const TasklistUpdateFieldSchema = Type.Union(
263
+ TASKLIST_UPDATE_FIELD_VALUES.map((field) => Type.Literal(field)),
264
+ );
265
+
266
+ const TasklistOriginOwnerRoleSchema = Type.Union(
267
+ [Type.Literal("editor"), Type.Literal("viewer"), Type.Literal("none")],
268
+ { description: "Role for original owner after owner transfer" },
269
+ );
270
+
271
+ const TasklistMemberSchema = Type.Object({
272
+ id: Type.String({ description: "Member ID (with type controlled by user_id_type)" }),
273
+ type: Type.Optional(Type.String({ description: "Member type (user/chat/app)" })),
274
+ role: Type.Optional(TasklistMemberRoleSchema),
275
+ name: Type.Optional(Type.String({ description: "Optional display name" })),
276
+ });
277
+
278
+ export const CreateTaskSchema = Type.Object({
279
+ summary: Type.String({ description: "Task title/summary" }),
280
+ description: Type.Optional(Type.String({ description: "Task description" })),
281
+ due: Type.Optional(TaskDateSchema),
282
+ start: Type.Optional(TaskDateSchema),
283
+ extra: Type.Optional(Type.String({ description: "Custom opaque metadata string" })),
284
+ completed_at: Type.Optional(
285
+ Type.String({
286
+ description: "Completion time as Unix timestamp in milliseconds (string, 13-digit ms)",
287
+ }),
288
+ ),
289
+ members: Type.Optional(Type.Array(TaskMemberSchema, { description: "Initial task members" })),
290
+ repeat_rule: Type.Optional(Type.String({ description: "Task repeat rule" })),
291
+ tasklists: Type.Optional(
292
+ Type.Array(TasklistRefSchema, { description: "Attach the task to tasklists/sections" }),
293
+ ),
294
+ mode: Type.Optional(Type.Number({ description: "Task mode value from Feishu Task API" })),
295
+ is_milestone: Type.Optional(Type.Boolean({ description: "Whether task is a milestone" })),
296
+ user_id_type: Type.Optional(
297
+ Type.String({
298
+ description: "User ID type for member IDs, e.g. open_id/user_id/union_id",
299
+ }),
300
+ ),
301
+ });
302
+
303
+ export const CreateSubtaskSchema = Type.Intersect([
304
+ Type.Object({
305
+ task_guid: Type.String({ description: "Parent task GUID" }),
306
+ }),
307
+ CreateTaskSchema,
308
+ ]);
309
+
310
+ export const DeleteTaskSchema = Type.Object({
311
+ task_guid: Type.String({ description: "Task GUID to delete" }),
312
+ });
313
+
314
+ export const GetTaskSchema = Type.Object({
315
+ task_guid: Type.String({ description: "Task GUID to retrieve" }),
316
+ user_id_type: Type.Optional(
317
+ Type.String({
318
+ description: "User ID type in returned members, e.g. open_id/user_id/union_id",
319
+ }),
320
+ ),
321
+ });
322
+
323
+ const TaskUpdateContentSchema = Type.Object(
324
+ {
325
+ summary: Type.Optional(Type.String({ description: "Updated summary" })),
326
+ description: Type.Optional(Type.String({ description: "Updated description" })),
327
+ due: Type.Optional(TaskDateSchema),
328
+ start: Type.Optional(TaskDateSchema),
329
+ extra: Type.Optional(Type.String({ description: "Updated extra metadata" })),
330
+ completed_at: Type.Optional(
331
+ Type.String({
332
+ description: "Updated completion time (Unix timestamp in milliseconds, string, 13-digit ms)",
333
+ }),
334
+ ),
335
+ repeat_rule: Type.Optional(Type.String({ description: "Updated repeat rule" })),
336
+ mode: Type.Optional(Type.Number({ description: "Updated task mode" })),
337
+ is_milestone: Type.Optional(Type.Boolean({ description: "Updated milestone flag" })),
338
+ },
339
+ { minProperties: 1 },
340
+ );
341
+
342
+ export const UpdateTaskSchema = Type.Object({
343
+ task_guid: Type.String({ description: "Task GUID to update" }),
344
+ task: TaskUpdateContentSchema,
345
+ update_fields: Type.Optional(
346
+ Type.Array(TaskUpdateFieldSchema, {
347
+ description:
348
+ "Fields to update. If omitted, this tool infers from keys in task (e.g. summary, description, due, start)",
349
+ minItems: 1,
350
+ uniqueItems: true,
351
+ }),
352
+ ),
353
+ user_id_type: Type.Optional(
354
+ Type.String({
355
+ description: "User ID type when task body contains user-related fields",
356
+ }),
357
+ ),
358
+ });
359
+
360
+ export const CreateTaskCommentSchema = Type.Object({
361
+ task_guid: Type.String({ description: "Task GUID to comment on" }),
362
+ content: Type.String({ description: "Comment content" }),
363
+ reply_to_comment_id: Type.Optional(
364
+ Type.String({ description: "Reply to a specific comment ID" }),
365
+ ),
366
+ user_id_type: Type.Optional(
367
+ Type.String({ description: "User ID type when comment involves user-related fields" }),
368
+ ),
369
+ });
370
+
371
+ export const ListTaskCommentsSchema = Type.Object({
372
+ task_guid: Type.String({ description: "Task GUID to list comments for" }),
373
+ page_size: Type.Optional(
374
+ Type.Number({
375
+ description: "Page size (1-100)",
376
+ minimum: 1,
377
+ maximum: 100,
378
+ }),
379
+ ),
380
+ page_token: Type.Optional(Type.String({ description: "Pagination token" })),
381
+ direction: Type.Optional(
382
+ Type.Union([Type.Literal("asc"), Type.Literal("desc")], {
383
+ description: "Sort direction",
384
+ }),
385
+ ),
386
+ user_id_type: Type.Optional(
387
+ Type.String({ description: "User ID type for returned creators" }),
388
+ ),
389
+ });
390
+
391
+ export const GetTaskCommentSchema = Type.Object({
392
+ comment_id: Type.String({ description: "Comment ID to retrieve" }),
393
+ user_id_type: Type.Optional(
394
+ Type.String({ description: "User ID type for returned creators" }),
395
+ ),
396
+ });
397
+
398
+ const TaskCommentUpdateContentSchema = Type.Object(
399
+ {
400
+ content: Type.Optional(Type.String({ description: "Updated comment content" })),
401
+ },
402
+ { minProperties: 1 },
403
+ );
404
+
405
+ export const UpdateTaskCommentSchema = Type.Object({
406
+ comment_id: Type.String({ description: "Comment ID to update" }),
407
+ comment: TaskCommentUpdateContentSchema,
408
+ update_fields: Type.Optional(
409
+ Type.Array(Type.String(), {
410
+ description: "Fields to update. If omitted, this tool infers from keys in comment (content)",
411
+ minItems: 1,
412
+ }),
413
+ ),
414
+ user_id_type: Type.Optional(
415
+ Type.String({ description: "User ID type for returned creators" }),
416
+ ),
417
+ });
418
+
419
+ export const DeleteTaskCommentSchema = Type.Object({
420
+ comment_id: Type.String({ description: "Comment ID to delete" }),
421
+ });
422
+
423
+ const TasklistUpdateContentSchema = Type.Object(
424
+ {
425
+ name: Type.Optional(Type.String({ description: "Updated tasklist name" })),
426
+ owner: Type.Optional(TasklistMemberSchema),
427
+ archive_tasklist: Type.Optional(Type.Boolean({ description: "Archive/unarchive tasklist" })),
428
+ },
429
+ { minProperties: 1 },
430
+ );
431
+
432
+ export const AddTaskToTasklistSchema = Type.Object({
433
+ task_guid: Type.String({ description: "Task GUID to move" }),
434
+ tasklist_guid: Type.String({ description: "Tasklist GUID to add the task into" }),
435
+ section_guid: Type.Optional(Type.String({ description: "Tasklist section GUID" })),
436
+ user_id_type: Type.Optional(
437
+ Type.String({ description: "User ID type when task body contains user-related fields" }),
438
+ ),
439
+ });
440
+
441
+ export const RemoveTaskFromTasklistSchema = Type.Object({
442
+ task_guid: Type.String({ description: "Task GUID to move" }),
443
+ tasklist_guid: Type.String({ description: "Tasklist GUID to remove the task from" }),
444
+ user_id_type: Type.Optional(
445
+ Type.String({ description: "User ID type when task body contains user-related fields" }),
446
+ ),
447
+ });
448
+
449
+ export const CreateTasklistSchema = Type.Object({
450
+ name: Type.String({ description: "Tasklist name" }),
451
+ members: Type.Optional(Type.Array(TasklistMemberSchema, { description: "Initial members" })),
452
+ archive_tasklist: Type.Optional(
453
+ Type.Boolean({ description: "Whether to create as archived tasklist" }),
454
+ ),
455
+ user_id_type: Type.Optional(
456
+ Type.String({ description: "User ID type for member IDs, e.g. open_id/user_id/union_id" }),
457
+ ),
458
+ });
459
+
460
+ export const GetTasklistSchema = Type.Object({
461
+ tasklist_guid: Type.String({ description: "Tasklist GUID to retrieve" }),
462
+ user_id_type: Type.Optional(
463
+ Type.String({ description: "User ID type in returned members, e.g. open_id/user_id/union_id" }),
464
+ ),
465
+ });
466
+
467
+ export const ListTasklistsSchema = Type.Object({
468
+ page_size: Type.Optional(
469
+ Type.Number({
470
+ description: "Page size (1-100)",
471
+ minimum: 1,
472
+ maximum: 100,
473
+ }),
474
+ ),
475
+ page_token: Type.Optional(Type.String({ description: "Pagination token" })),
476
+ user_id_type: Type.Optional(
477
+ Type.String({ description: "User ID type in returned members, e.g. open_id/user_id/union_id" }),
478
+ ),
479
+ });
480
+
481
+ export const UpdateTasklistSchema = Type.Object({
482
+ tasklist_guid: Type.String({ description: "Tasklist GUID to update" }),
483
+ tasklist: TasklistUpdateContentSchema,
484
+ update_fields: Type.Optional(
485
+ Type.Array(TasklistUpdateFieldSchema, {
486
+ description:
487
+ "Fields to update. If omitted, this tool infers from keys in tasklist (e.g. name, owner, archive_tasklist)",
488
+ minItems: 1,
489
+ uniqueItems: true,
490
+ }),
491
+ ),
492
+ origin_owner_to_role: Type.Optional(TasklistOriginOwnerRoleSchema),
493
+ user_id_type: Type.Optional(
494
+ Type.String({ description: "User ID type when tasklist body contains user-related fields" }),
495
+ ),
496
+ });
497
+
498
+ export const DeleteTasklistSchema = Type.Object({
499
+ tasklist_guid: Type.String({ description: "Tasklist GUID to delete" }),
500
+ });
501
+
502
+ export const AddTasklistMembersSchema = Type.Object({
503
+ tasklist_guid: Type.String({ description: "Tasklist GUID to add members to" }),
504
+ members: Type.Array(TasklistMemberSchema, {
505
+ description: "Members to add",
506
+ minItems: 1,
507
+ }),
508
+ user_id_type: Type.Optional(
509
+ Type.String({ description: "User ID type for member IDs, e.g. open_id/user_id/union_id" }),
510
+ ),
511
+ });
512
+
513
+ export const RemoveTasklistMembersSchema = Type.Object({
514
+ tasklist_guid: Type.String({ description: "Tasklist GUID to remove members from" }),
515
+ members: Type.Array(TasklistMemberSchema, {
516
+ description: "Members to remove",
517
+ minItems: 1,
518
+ }),
519
+ user_id_type: Type.Optional(
520
+ Type.String({ description: "User ID type for member IDs, e.g. open_id/user_id/union_id" }),
521
+ ),
522
+ });
523
+
524
+ export const UploadTaskAttachmentSchema = Type.Union([
525
+ Type.Object({
526
+ task_guid: Type.String({ description: "Task GUID to upload attachment to" }),
527
+ file_path: Type.String({ description: "Local file path on the OpenClaw host" }),
528
+ user_id_type: Type.Optional(
529
+ Type.String({ description: "User ID type for returned uploader" }),
530
+ ),
531
+ }),
532
+ Type.Object({
533
+ task_guid: Type.String({ description: "Task GUID to upload attachment to" }),
534
+ file_url: Type.String({ description: "Remote file URL to download and upload" }),
535
+ filename: Type.Optional(Type.String({ description: "Override filename for uploaded attachment" })),
536
+ user_id_type: Type.Optional(
537
+ Type.String({ description: "User ID type for returned uploader" }),
538
+ ),
539
+ }),
540
+ ]);
541
+
542
+ export const ListTaskAttachmentsSchema = Type.Object({
543
+ task_guid: Type.String({ description: "Task GUID to list attachments for" }),
544
+ page_size: Type.Optional(
545
+ Type.Number({
546
+ description: "Page size (1-100)",
547
+ minimum: 1,
548
+ maximum: 100,
549
+ }),
550
+ ),
551
+ page_token: Type.Optional(Type.String({ description: "Pagination token" })),
552
+ updated_mesc: Type.Optional(Type.String({ description: "Updated timestamp filter" })),
553
+ user_id_type: Type.Optional(
554
+ Type.String({ description: "User ID type for returned uploader" }),
555
+ ),
556
+ });
557
+
558
+ export const GetTaskAttachmentSchema = Type.Object({
559
+ attachment_guid: Type.String({ description: "Attachment GUID to retrieve" }),
560
+ user_id_type: Type.Optional(
561
+ Type.String({ description: "User ID type for returned uploader" }),
562
+ ),
563
+ });
564
+
565
+ export const DeleteTaskAttachmentSchema = Type.Object({
566
+ attachment_guid: Type.String({ description: "Attachment GUID to delete" }),
567
+ });
@@ -0,0 +1,104 @@
1
+ const FENCED_CODE_BLOCK_RE = /(```[\s\S]*?```)/g;
2
+ const INLINE_CODE_RE = /(`[^`\n]*`)/g;
3
+ const URL_RE = /https?:\/\/[^\s<>"'`]+/g;
4
+ const TRAILING_PUNCT_RE = /[.,;!?\u3002\uff0c\uff1b\uff01\uff1f\u3001]/u;
5
+ const AUTO_LINK_RE = /<\s*(https?:\/\/[^>\s]+)\s*>/g;
6
+
7
+ // Feishu markdown can mis-handle some URL characters in edge cases.
8
+ // Encode a minimal safe subset while preserving URL semantics.
9
+ function normalizeUrlForFeishu(url: string): string {
10
+ return url.replace(/_/g, "%5F").replace(/\(/g, "%28").replace(/\)/g, "%29");
11
+ }
12
+
13
+ // We intentionally convert raw/autolink URLs into explicit markdown links.
14
+ // Why: in Feishu message rendering, plain URLs (including "<...>" autolinks)
15
+ // can be re-tokenized and visually split/truncated on characters like "_"
16
+ // or around long query strings. The explicit "[label](url)" form is more stable
17
+ // in post/card markdown parsing and keeps the link clickable end-to-end.
18
+ function buildMarkdownLink(url: string): string {
19
+ const label = url.replace(/[\[\]]/g, "\\$&");
20
+ return `[${label}](${url})`;
21
+ }
22
+
23
+ // We only need balance info to detect whether a trailing ")" belongs to the URL.
24
+ function countParens(text: string): { open: number; close: number } {
25
+ let open = 0;
26
+ let close = 0;
27
+ for (const c of text) {
28
+ if (c === "(") {
29
+ open += 1;
30
+ } else if (c === ")") {
31
+ close += 1;
32
+ }
33
+ }
34
+ return { open, close };
35
+ }
36
+
37
+ function splitTrailingPunctuation(rawUrl: string): { url: string; trailing: string } {
38
+ let url = rawUrl;
39
+ let trailing = "";
40
+ let { open, close } = countParens(rawUrl);
41
+
42
+ while (url.length > 0) {
43
+ const tail = url.slice(-1);
44
+ // Many links appear as ".../path),". Strip punctuation that is not part of the URL.
45
+ const closeParenOverflow = tail === ")" && close > open;
46
+ if (!TRAILING_PUNCT_RE.test(tail) && !closeParenOverflow) {
47
+ break;
48
+ }
49
+ if (tail === ")") {
50
+ close -= 1;
51
+ }
52
+ trailing = tail + trailing;
53
+ url = url.slice(0, -1);
54
+ }
55
+
56
+ return { url, trailing };
57
+ }
58
+
59
+ function wrapBareUrls(text: string): string {
60
+ // Normalize "<https://...>" to explicit markdown links for better Feishu stability.
61
+ const convertedAutoLinks = text.replace(AUTO_LINK_RE, (_full, rawUrl: string) => {
62
+ const { url, trailing } = splitTrailingPunctuation(rawUrl);
63
+ if (!url) {
64
+ return _full;
65
+ }
66
+ return `${buildMarkdownLink(normalizeUrlForFeishu(url))}${trailing}`;
67
+ });
68
+
69
+ return convertedAutoLinks.replace(URL_RE, (raw, offset, input) => {
70
+ const { url, trailing } = splitTrailingPunctuation(raw);
71
+ if (!url) {
72
+ return raw;
73
+ }
74
+
75
+ // Do not rebuild existing markdown destinations, only normalize URL chars in-place.
76
+ const isMarkdownDestination = offset >= 2 && input.slice(offset - 2, offset) === "](";
77
+ const normalizedUrl = normalizeUrlForFeishu(url);
78
+ if (isMarkdownDestination) {
79
+ return `${normalizedUrl}${trailing}`;
80
+ }
81
+
82
+ return `${buildMarkdownLink(normalizedUrl)}${trailing}`;
83
+ });
84
+ }
85
+
86
+ function normalizeNonCodeSegments(text: string): string {
87
+ // Keep inline code untouched, normalize only plain markdown text.
88
+ return text
89
+ .split(INLINE_CODE_RE)
90
+ .map((segment, idx) => (idx % 2 === 1 && segment.startsWith("`") ? segment : wrapBareUrls(segment)))
91
+ .join("");
92
+ }
93
+
94
+ export function normalizeFeishuMarkdownLinks(text: string): string {
95
+ if (!text || (!text.includes("http://") && !text.includes("https://"))) {
96
+ return text;
97
+ }
98
+
99
+ return text
100
+ // Keep fenced code blocks untouched to avoid changing examples/snippets.
101
+ .split(FENCED_CODE_BLOCK_RE)
102
+ .map((block, idx) => (idx % 2 === 1 && block.startsWith("```") ? block : normalizeNonCodeSegments(block)))
103
+ .join("");
104
+ }