@m1heng-clawd/feishu 0.1.15 → 0.1.17

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 (39) hide show
  1. package/README.md +28 -0
  2. package/index.ts +4 -0
  3. package/package.json +2 -1
  4. package/skills/feishu-message/SKILL.md +111 -0
  5. package/skills/feishu-reaction/SKILL.md +120 -0
  6. package/src/bot.ts +4 -0
  7. package/src/channel.ts +2 -0
  8. package/src/chat-tools/actions.ts +102 -33
  9. package/src/chat-tools/schemas.ts +57 -62
  10. package/src/config-schema.ts +2 -0
  11. package/src/doc-tools/actions.ts +63 -14
  12. package/src/doc-tools/schemas.ts +41 -77
  13. package/src/doc-write-service.ts +19 -0
  14. package/src/drive-tools/actions.ts +18 -6
  15. package/src/drive-tools/register.ts +1 -1
  16. package/src/drive-tools/schemas.ts +45 -58
  17. package/src/media-duration.ts +43 -8
  18. package/src/media.ts +31 -53
  19. package/src/message-tools/actions.ts +154 -0
  20. package/src/message-tools/index.ts +2 -0
  21. package/src/message-tools/register.ts +58 -0
  22. package/src/message-tools/schemas.ts +56 -0
  23. package/src/outbound.ts +2 -2
  24. package/src/perm-tools/actions.ts +27 -3
  25. package/src/perm-tools/schemas.ts +39 -45
  26. package/src/reaction-tools/actions.ts +127 -0
  27. package/src/reaction-tools/index.ts +2 -0
  28. package/src/reaction-tools/register.ts +51 -0
  29. package/src/reaction-tools/schemas.ts +34 -0
  30. package/src/reply-dispatcher.ts +23 -33
  31. package/src/streaming-card.ts +34 -11
  32. package/src/task-tools/actions.ts +145 -10
  33. package/src/task-tools/register.ts +56 -1
  34. package/src/task-tools/schemas.ts +23 -18
  35. package/src/tools-common/tool-context.ts +2 -0
  36. package/src/tools-config.ts +2 -0
  37. package/src/types.ts +4 -0
  38. package/src/wiki-tools/actions.ts +24 -6
  39. package/src/wiki-tools/schemas.ts +32 -51
@@ -6,19 +6,24 @@ import {
6
6
  addTaskToTasklist,
7
7
  addTasklistMembers,
8
8
  createSubtask,
9
+ createTaskComment,
9
10
  createTask,
10
11
  createTasklist,
11
12
  deleteTaskAttachment,
13
+ deleteTaskComment,
12
14
  deleteTask,
13
15
  deleteTasklist,
14
16
  getTaskAttachment,
17
+ getTaskComment,
15
18
  getTask,
16
19
  getTasklist,
20
+ listTaskComments,
17
21
  listTasklists,
18
22
  removeTaskFromTasklist,
19
23
  removeTasklistMembers,
20
24
  listTaskAttachments,
21
25
  uploadTaskAttachment,
26
+ updateTaskComment,
22
27
  updateTask,
23
28
  updateTasklist,
24
29
  } from "./actions.js";
@@ -36,26 +41,36 @@ import {
36
41
  type CreateTasklistParams,
37
42
  DeleteTaskAttachmentSchema,
38
43
  type DeleteTaskAttachmentParams,
44
+ DeleteTaskCommentSchema,
45
+ type DeleteTaskCommentParams,
39
46
  DeleteTaskSchema,
40
47
  type DeleteTaskParams,
41
48
  DeleteTasklistSchema,
42
49
  type DeleteTasklistParams,
43
50
  GetTaskAttachmentSchema,
44
51
  type GetTaskAttachmentParams,
52
+ GetTaskCommentSchema,
53
+ type GetTaskCommentParams,
45
54
  GetTaskSchema,
46
55
  type GetTaskParams,
47
56
  GetTasklistSchema,
48
57
  type GetTasklistParams,
49
58
  ListTaskAttachmentsSchema,
50
59
  type ListTaskAttachmentsParams,
60
+ ListTaskCommentsSchema,
61
+ type ListTaskCommentsParams,
51
62
  ListTasklistsSchema,
52
63
  type ListTasklistsParams,
53
64
  RemoveTaskFromTasklistSchema,
54
65
  type RemoveTaskFromTasklistParams,
55
66
  RemoveTasklistMembersSchema,
56
67
  type RemoveTasklistMembersParams,
68
+ CreateTaskCommentSchema,
69
+ type CreateTaskCommentParams,
57
70
  UploadTaskAttachmentSchema,
58
71
  type UploadTaskAttachmentParams,
72
+ UpdateTaskCommentSchema,
73
+ type UpdateTaskCommentParams,
59
74
  UpdateTaskSchema,
60
75
  UpdateTasklistSchema,
61
76
  type UpdateTaskParams,
@@ -259,5 +274,45 @@ export function registerFeishuTaskTools(api: OpenClawPluginApi) {
259
274
  run: async ({ client }, params) => updateTask(client, params),
260
275
  });
261
276
 
262
- api.logger.debug?.("feishu_task: Registered task, tasklist, subtask, and attachment tools");
277
+ registerTaskTool<CreateTaskCommentParams>(api, {
278
+ name: "feishu_task_comment_create",
279
+ label: "Feishu Task Comment Create",
280
+ description: "Create a comment on a Feishu task (task v2)",
281
+ parameters: CreateTaskCommentSchema,
282
+ run: async ({ client }, params) => createTaskComment(client, params),
283
+ });
284
+
285
+ registerTaskTool<ListTaskCommentsParams>(api, {
286
+ name: "feishu_task_comment_list",
287
+ label: "Feishu Task Comment List",
288
+ description: "List comments for a Feishu task (task v2)",
289
+ parameters: ListTaskCommentsSchema,
290
+ run: async ({ client }, params) => listTaskComments(client, params),
291
+ });
292
+
293
+ registerTaskTool<GetTaskCommentParams>(api, {
294
+ name: "feishu_task_comment_get",
295
+ label: "Feishu Task Comment Get",
296
+ description: "Get a Feishu task comment by comment_id (task v2)",
297
+ parameters: GetTaskCommentSchema,
298
+ run: async ({ client }, params) => getTaskComment(client, params),
299
+ });
300
+
301
+ registerTaskTool<UpdateTaskCommentParams>(api, {
302
+ name: "feishu_task_comment_update",
303
+ label: "Feishu Task Comment Update",
304
+ description: "Update a Feishu task comment by comment_id (task v2 patch)",
305
+ parameters: UpdateTaskCommentSchema,
306
+ run: async ({ client }, params) => updateTaskComment(client, params),
307
+ });
308
+
309
+ registerTaskTool<DeleteTaskCommentParams>(api, {
310
+ name: "feishu_task_comment_delete",
311
+ label: "Feishu Task Comment Delete",
312
+ description: "Delete a Feishu task comment by comment_id (task v2)",
313
+ parameters: DeleteTaskCommentSchema,
314
+ run: async ({ client }, params) => deleteTaskComment(client, params),
315
+ });
316
+
317
+ api.logger.debug?.("feishu_task: Registered task, comment, tasklist, subtask, and attachment tools");
263
318
  }
@@ -1,6 +1,15 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { TaskClient } from "./common.js";
3
3
 
4
+ // NOTE: Avoid Type.Union([Type.Literal(...)]) which compiles to anyOf.
5
+ // Some providers reject anyOf in tool schemas; a flat string enum is safer.
6
+ function stringEnum<T extends readonly string[]>(
7
+ values: T,
8
+ options: { description?: string; default?: T[number] } = {},
9
+ ) {
10
+ return Type.Unsafe<T[number]>({ type: "string", enum: [...values], ...options });
11
+ }
12
+
4
13
  type TaskCreatePayload = NonNullable<Parameters<TaskClient["task"]["v2"]["task"]["create"]>[0]>;
5
14
  type TaskUpdatePayload = NonNullable<Parameters<TaskClient["task"]["v2"]["task"]["patch"]>[0]>;
6
15
  type TaskDeletePayload = NonNullable<Parameters<TaskClient["task"]["v2"]["task"]["delete"]>[0]>;
@@ -75,6 +84,7 @@ export const TASK_UPDATE_FIELD_VALUES = [
75
84
  "mode",
76
85
  "is_milestone",
77
86
  ] as const;
87
+ export const TASK_COMMENT_UPDATE_FIELD_VALUES = ["content"] as const;
78
88
  export const TASKLIST_UPDATE_FIELD_VALUES = ["name", "owner", "archive_tasklist"] as const;
79
89
 
80
90
  export type CreateTaskParams = {
@@ -245,23 +255,19 @@ const TasklistRefSchema = Type.Object({
245
255
  section_guid: Type.Optional(Type.String({ description: "Section GUID in tasklist" })),
246
256
  });
247
257
 
248
- const TaskUpdateFieldSchema = Type.Union(
249
- TASK_UPDATE_FIELD_VALUES.map((field) => Type.Literal(field)),
250
- );
258
+ const TaskUpdateFieldSchema = stringEnum(TASK_UPDATE_FIELD_VALUES);
251
259
 
252
- const TasklistMemberRoleSchema = Type.Union(
253
- [Type.Literal("owner"), Type.Literal("editor"), Type.Literal("viewer")],
254
- { description: "Member role (owner/editor/viewer)" },
255
- );
260
+ const TaskCommentUpdateFieldSchema = stringEnum(TASK_COMMENT_UPDATE_FIELD_VALUES);
256
261
 
257
- const TasklistUpdateFieldSchema = Type.Union(
258
- TASKLIST_UPDATE_FIELD_VALUES.map((field) => Type.Literal(field)),
259
- );
262
+ const TasklistMemberRoleSchema = stringEnum(["owner", "editor", "viewer"] as const, {
263
+ description: "Member role (owner/editor/viewer)",
264
+ });
260
265
 
261
- const TasklistOriginOwnerRoleSchema = Type.Union(
262
- [Type.Literal("editor"), Type.Literal("viewer"), Type.Literal("none")],
263
- { description: "Role for original owner after owner transfer" },
264
- );
266
+ const TasklistUpdateFieldSchema = stringEnum(TASKLIST_UPDATE_FIELD_VALUES);
267
+
268
+ const TasklistOriginOwnerRoleSchema = stringEnum(["editor", "viewer", "none"] as const, {
269
+ description: "Role for original owner after owner transfer",
270
+ });
265
271
 
266
272
  const TasklistMemberSchema = Type.Object({
267
273
  id: Type.String({ description: "Member ID (with type controlled by user_id_type)" }),
@@ -372,9 +378,7 @@ export const ListTaskCommentsSchema = Type.Object({
372
378
  ),
373
379
  page_token: Type.Optional(Type.String({ description: "Pagination token" })),
374
380
  direction: Type.Optional(
375
- Type.Union([Type.Literal("asc"), Type.Literal("desc")], {
376
- description: "Sort direction",
377
- }),
381
+ stringEnum(["asc", "desc"] as const, { description: "Sort direction" }),
378
382
  ),
379
383
  user_id_type: Type.Optional(
380
384
  Type.String({ description: "User ID type for returned creators" }),
@@ -399,9 +403,10 @@ export const UpdateTaskCommentSchema = Type.Object({
399
403
  comment_id: Type.String({ description: "Comment ID to update" }),
400
404
  comment: TaskCommentUpdateContentSchema,
401
405
  update_fields: Type.Optional(
402
- Type.Array(Type.String(), {
406
+ Type.Array(TaskCommentUpdateFieldSchema, {
403
407
  description: "Fields to update. If omitted, this tool infers from keys in comment (content)",
404
408
  minItems: 1,
409
+ uniqueItems: true,
405
410
  }),
406
411
  ),
407
412
  user_id_type: Type.Optional(
@@ -4,6 +4,8 @@ export type FeishuToolContext = {
4
4
  channel: "feishu";
5
5
  accountId: string;
6
6
  sessionKey?: string;
7
+ senderOpenId?: string;
8
+ chatId?: string;
7
9
  };
8
10
 
9
11
  const toolContextStorage = new AsyncLocalStorage<FeishuToolContext>();
@@ -14,6 +14,8 @@ export const DEFAULT_TOOLS_CONFIG: Required<FeishuToolsConfig> = {
14
14
  task: true,
15
15
  chat: true,
16
16
  urgent: true,
17
+ message: true,
18
+ reaction: true,
17
19
  };
18
20
 
19
21
  /**
package/src/types.ts CHANGED
@@ -76,6 +76,10 @@ export type FeishuToolsConfig = {
76
76
  chat?: boolean;
77
77
  /** Enable the feishu_urgent tool (buzz/urgent notifications). Enabled by default. */
78
78
  urgent?: boolean;
79
+ /** Enable the feishu_message tool (get/list messages). Enabled by default. */
80
+ message?: boolean;
81
+ /** Enable the feishu_reaction tool (add/remove/list emoji reactions). Enabled by default. */
82
+ reaction?: boolean;
79
83
  };
80
84
 
81
85
  export type DynamicAgentCreationConfig = {
@@ -7,6 +7,13 @@ const WIKI_ACCESS_HINT =
7
7
  "To grant wiki access: Open wiki space -> Settings -> Members -> Add the bot. " +
8
8
  "See: https://open.feishu.cn/document/server-docs/docs/wiki-v2/wiki-qa#a40ad4ca";
9
9
 
10
+ function requireString(value: unknown, field: string): string {
11
+ if (typeof value !== "string" || value.trim().length === 0) {
12
+ throw new Error(`${field} is required`);
13
+ }
14
+ return value;
15
+ }
16
+
10
17
  async function listSpaces(client: WikiClient) {
11
18
  const res = await runWikiApiCall("wiki.space.list", () => client.wiki.space.list({}));
12
19
  const spaces =
@@ -140,26 +147,37 @@ export async function runWikiAction(client: WikiClient, params: FeishuWikiParams
140
147
  case "spaces":
141
148
  return listSpaces(client);
142
149
  case "nodes":
143
- return listNodes(client, params.space_id, params.parent_node_token);
150
+ return listNodes(client, requireString(params.space_id, "space_id"), params.parent_node_token);
144
151
  case "get":
145
- return getNode(client, params.token);
152
+ return getNode(client, requireString(params.token, "token"));
146
153
  case "search":
147
154
  return {
148
155
  error:
149
156
  "Search is not available. Use feishu_wiki with action: 'nodes' to browse or action: 'get' to lookup by token.",
150
157
  };
151
158
  case "create":
152
- return createNode(client, params.space_id, params.title, params.obj_type, params.parent_node_token);
159
+ return createNode(
160
+ client,
161
+ requireString(params.space_id, "space_id"),
162
+ requireString(params.title, "title"),
163
+ params.obj_type,
164
+ params.parent_node_token,
165
+ );
153
166
  case "move":
154
167
  return moveNode(
155
168
  client,
156
- params.space_id,
157
- params.node_token,
169
+ requireString(params.space_id, "space_id"),
170
+ requireString(params.node_token, "node_token"),
158
171
  params.target_space_id,
159
172
  params.target_parent_token,
160
173
  );
161
174
  case "rename":
162
- return renameNode(client, params.space_id, params.node_token, params.title);
175
+ return renameNode(
176
+ client,
177
+ requireString(params.space_id, "space_id"),
178
+ requireString(params.node_token, "node_token"),
179
+ requireString(params.title, "title"),
180
+ );
163
181
  default:
164
182
  return { error: `Unknown action: ${(params as any).action}` };
165
183
  }
@@ -1,55 +1,36 @@
1
1
  import { Type, type Static } from "@sinclair/typebox";
2
2
 
3
- export const FeishuWikiSchema = Type.Union([
4
- Type.Object({
5
- action: Type.Literal("spaces"),
6
- }),
7
- Type.Object({
8
- action: Type.Literal("nodes"),
9
- space_id: Type.String({ description: "Knowledge space ID" }),
10
- parent_node_token: Type.Optional(
11
- Type.String({ description: "Parent node token (optional, omit for root)" }),
12
- ),
13
- }),
14
- Type.Object({
15
- action: Type.Literal("get"),
16
- token: Type.String({ description: "Wiki node token (from URL /wiki/XXX)" }),
17
- }),
18
- Type.Object({
19
- action: Type.Literal("search"),
20
- query: Type.String({ description: "Search query" }),
21
- space_id: Type.Optional(Type.String({ description: "Limit search to this space (optional)" })),
22
- }),
23
- Type.Object({
24
- action: Type.Literal("create"),
25
- space_id: Type.String({ description: "Knowledge space ID" }),
26
- title: Type.String({ description: "Node title" }),
27
- obj_type: Type.Optional(
28
- Type.Union([Type.Literal("docx"), Type.Literal("sheet"), Type.Literal("bitable")], {
29
- description: "Object type (default: docx)",
30
- }),
31
- ),
32
- parent_node_token: Type.Optional(
33
- Type.String({ description: "Parent node token (optional, omit for root)" }),
34
- ),
35
- }),
36
- Type.Object({
37
- action: Type.Literal("move"),
38
- space_id: Type.String({ description: "Source knowledge space ID" }),
39
- node_token: Type.String({ description: "Node token to move" }),
40
- target_space_id: Type.Optional(
41
- Type.String({ description: "Target space ID (optional, same space if omitted)" }),
42
- ),
43
- target_parent_token: Type.Optional(
44
- Type.String({ description: "Target parent node token (optional, root if omitted)" }),
45
- ),
46
- }),
47
- Type.Object({
48
- action: Type.Literal("rename"),
49
- space_id: Type.String({ description: "Knowledge space ID" }),
50
- node_token: Type.String({ description: "Node token to rename" }),
51
- title: Type.String({ description: "New title" }),
52
- }),
53
- ]);
3
+ function stringEnum<T extends readonly string[]>(
4
+ values: T,
5
+ options: { description?: string; default?: T[number] } = {},
6
+ ) {
7
+ return Type.Unsafe<T[number]>({ type: "string", enum: [...values], ...options });
8
+ }
9
+
10
+ const WIKI_ACTION_VALUES = ["spaces", "nodes", "get", "search", "create", "move", "rename"] as const;
11
+ const WIKI_OBJ_TYPE_VALUES = ["docx", "sheet", "bitable"] as const;
12
+
13
+ export const FeishuWikiSchema = Type.Object({
14
+ action: stringEnum(WIKI_ACTION_VALUES, { description: "Wiki action" }),
15
+ space_id: Type.Optional(Type.String({ description: "Knowledge space ID" })),
16
+ parent_node_token: Type.Optional(
17
+ Type.String({ description: "Parent node token (optional, omit for root)" }),
18
+ ),
19
+ token: Type.Optional(Type.String({ description: "Wiki node token (from URL /wiki/XXX)" })),
20
+ query: Type.Optional(Type.String({ description: "Search query" })),
21
+ title: Type.Optional(Type.String({ description: "Node title / new title" })),
22
+ obj_type: Type.Optional(
23
+ stringEnum(WIKI_OBJ_TYPE_VALUES, {
24
+ description: "Object type for create action (default: docx)",
25
+ }),
26
+ ),
27
+ node_token: Type.Optional(Type.String({ description: "Node token" })),
28
+ target_space_id: Type.Optional(
29
+ Type.String({ description: "Target space ID (optional, same space if omitted)" }),
30
+ ),
31
+ target_parent_token: Type.Optional(
32
+ Type.String({ description: "Target parent node token (optional, root if omitted)" }),
33
+ ),
34
+ });
54
35
 
55
36
  export type FeishuWikiParams = Static<typeof FeishuWikiSchema>;