@m1heng-clawd/feishu 0.1.14 → 0.1.16

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 (43) hide show
  1. package/README.md +82 -2
  2. package/index.ts +7 -2
  3. package/package.json +5 -4
  4. package/skills/feishu-chat/SKILL.md +139 -0
  5. package/skills/feishu-urgent/SKILL.md +107 -0
  6. package/src/bot.ts +391 -45
  7. package/src/channel.ts +2 -0
  8. package/src/chat-tools/actions.ts +502 -0
  9. package/src/chat-tools/common.ts +13 -0
  10. package/src/chat-tools/index.ts +2 -0
  11. package/src/chat-tools/register.ts +72 -0
  12. package/src/chat-tools/schemas.ts +61 -0
  13. package/src/client.ts +13 -0
  14. package/src/config-schema.ts +6 -0
  15. package/src/doc-tools/actions.ts +63 -14
  16. package/src/doc-tools/schemas.ts +41 -77
  17. package/src/doc-write-service.ts +19 -0
  18. package/src/drive-tools/actions.ts +75 -27
  19. package/src/drive-tools/schemas.ts +45 -58
  20. package/src/media-duration.ts +190 -0
  21. package/src/media.ts +50 -27
  22. package/src/mention.ts +1 -1
  23. package/src/monitor.ts +32 -8
  24. package/src/outbound.ts +2 -2
  25. package/src/perm-tools/actions.ts +27 -3
  26. package/src/perm-tools/schemas.ts +39 -45
  27. package/src/policy.ts +7 -3
  28. package/src/reply-dispatcher.ts +97 -13
  29. package/src/send.ts +360 -42
  30. package/src/streaming-card.ts +32 -4
  31. package/src/task-tools/actions.ts +145 -10
  32. package/src/task-tools/register.ts +56 -1
  33. package/src/task-tools/schemas.ts +23 -18
  34. package/src/tools-common/tool-context.ts +1 -0
  35. package/src/tools-config.ts +9 -2
  36. package/src/types.ts +8 -1
  37. package/src/typing.ts +115 -5
  38. package/src/urgent-tools/actions.ts +84 -0
  39. package/src/urgent-tools/index.ts +3 -0
  40. package/src/urgent-tools/register.ts +64 -0
  41. package/src/urgent-tools/schemas.ts +25 -0
  42. package/src/wiki-tools/actions.ts +24 -6
  43. package/src/wiki-tools/schemas.ts +32 -51
@@ -0,0 +1,84 @@
1
+ import type * as Lark from "@larksuiteoapi/node-sdk";
2
+ import { runFeishuApiCall, type FeishuApiResponse } from "../tools-common/feishu-api.js";
3
+
4
+ /**
5
+ * Urgency type for Feishu urgent messages.
6
+ * - "app": In-app buzz notification (default, no extra cost)
7
+ * - "sms": SMS push to the recipient's phone
8
+ * - "phone": Voice call to the recipient's phone
9
+ */
10
+ export type FeishuUrgentType = "app" | "sms" | "phone";
11
+
12
+ type UrgentPayload = {
13
+ data: { user_id_list: string[] };
14
+ params: { user_id_type: "open_id" | "user_id" | "union_id" };
15
+ path: { message_id: string };
16
+ };
17
+
18
+ interface UrgentResponse extends FeishuApiResponse {
19
+ data?: { invalid_user_id_list?: string[] };
20
+ }
21
+
22
+ type LarkMessageWithUrgent = Lark.Client["im"]["message"] & {
23
+ urgentApp?: (payload: UrgentPayload) => Promise<UrgentResponse>;
24
+ urgentSms?: (payload: UrgentPayload) => Promise<UrgentResponse>;
25
+ urgentPhone?: (payload: UrgentPayload) => Promise<UrgentResponse>;
26
+ };
27
+
28
+ /**
29
+ * Send an urgent (buzz) notification for an existing Feishu message.
30
+ *
31
+ * Calls the Feishu "urgent" API which sends a strong push notification
32
+ * to the specified recipients. The message must already be sent.
33
+ *
34
+ * Requires `im:message.urgent` scope (or `im:message.urgent:sms` / `im:message.urgent:phone` variants).
35
+ *
36
+ * Common errors:
37
+ * - Code 230024: Quota exceeded ("Reach the upper limit of urgent message").
38
+ * Check tenant quota in Feishu admin console > Cost Center.
39
+ * - Invalid user IDs cause HTTP 400 with descriptive message (not returned in
40
+ * `invalid_user_id_list` as documented).
41
+ *
42
+ * @see https://open.feishu.cn/document/server-docs/im-v1/message/urgent_app
43
+ * @see https://open.feishu.cn/document/server-docs/im-v1/message/urgent_sms
44
+ * @see https://open.feishu.cn/document/server-docs/im-v1/message/urgent_phone
45
+ */
46
+ export async function urgentMessageFeishu(params: {
47
+ client: Lark.Client;
48
+ messageId: string;
49
+ userIds: string[];
50
+ urgentType?: FeishuUrgentType;
51
+ }): Promise<{ invalidUserList: string[] }> {
52
+ const { client, messageId, userIds, urgentType = "app" } = params;
53
+
54
+ const larkMessage = client.im.message as LarkMessageWithUrgent;
55
+
56
+ const payload: UrgentPayload = {
57
+ path: { message_id: messageId },
58
+ params: { user_id_type: "open_id" },
59
+ data: { user_id_list: userIds },
60
+ };
61
+
62
+ const methodMap = {
63
+ app: larkMessage.urgentApp?.bind(larkMessage),
64
+ sms: larkMessage.urgentSms?.bind(larkMessage),
65
+ phone: larkMessage.urgentPhone?.bind(larkMessage),
66
+ } as const;
67
+
68
+ const method = methodMap[urgentType];
69
+ if (typeof method !== "function") {
70
+ throw new Error(
71
+ `Feishu urgent: SDK method not available for urgentType="${urgentType}". ` +
72
+ `Check that @larksuiteoapi/node-sdk is up to date.`,
73
+ );
74
+ }
75
+
76
+ const response = await runFeishuApiCall<UrgentResponse>(
77
+ `Feishu urgent message (${urgentType})`,
78
+ () => method(payload),
79
+ );
80
+
81
+ return {
82
+ invalidUserList: response.data?.invalid_user_id_list ?? [],
83
+ };
84
+ }
@@ -0,0 +1,3 @@
1
+ export { registerFeishuUrgentTools } from "./register.js";
2
+ export { urgentMessageFeishu, type FeishuUrgentType } from "./actions.js";
3
+ export { FeishuUrgentSchema, type FeishuUrgentParams } from "./schemas.js";
@@ -0,0 +1,64 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { hasFeishuToolEnabledForAnyAccount, withFeishuToolClient } from "../tools-common/tool-exec.js";
3
+ import { errorResult, json } from "../tools-common/feishu-api.js";
4
+ import { urgentMessageFeishu } from "./actions.js";
5
+ import { FeishuUrgentSchema, type FeishuUrgentParams } from "./schemas.js";
6
+
7
+ export function registerFeishuUrgentTools(api: OpenClawPluginApi) {
8
+ if (!api.config) {
9
+ api.logger.debug?.("feishu_urgent: No config available, skipping");
10
+ return;
11
+ }
12
+
13
+ if (!hasFeishuToolEnabledForAnyAccount(api.config)) {
14
+ api.logger.debug?.("feishu_urgent: No Feishu accounts configured, skipping");
15
+ return;
16
+ }
17
+
18
+ if (!hasFeishuToolEnabledForAnyAccount(api.config, "urgent")) {
19
+ api.logger.debug?.("feishu_urgent: urgent tool disabled in config");
20
+ return;
21
+ }
22
+
23
+ api.registerTool(
24
+ {
25
+ name: "feishu_urgent",
26
+ label: "Feishu Urgent",
27
+ description:
28
+ "Send an urgent (buzz) notification for an existing Feishu message. " +
29
+ "Supported urgent_type values: app (in-app buzz, default), sms (SMS push), phone (voice call). " +
30
+ "Requires the message_id of an already-sent message and the open_id list of recipients to buzz. " +
31
+ "Use this to escalate important messages that require immediate attention.",
32
+ parameters: FeishuUrgentSchema,
33
+ async execute(_toolCallId, params) {
34
+ const p = params as FeishuUrgentParams;
35
+ try {
36
+ return await withFeishuToolClient({
37
+ api,
38
+ toolName: "feishu_urgent",
39
+ requiredTool: "urgent",
40
+ run: async ({ client }) => {
41
+ const result = await urgentMessageFeishu({
42
+ client,
43
+ messageId: p.message_id,
44
+ userIds: p.user_ids,
45
+ urgentType: p.urgent_type ?? "app",
46
+ });
47
+ return json({
48
+ ok: true,
49
+ message_id: p.message_id,
50
+ urgent_type: p.urgent_type ?? "app",
51
+ invalid_user_list: result.invalidUserList,
52
+ });
53
+ },
54
+ });
55
+ } catch (err) {
56
+ return errorResult(err);
57
+ }
58
+ },
59
+ },
60
+ { name: "feishu_urgent" },
61
+ );
62
+
63
+ api.logger.debug?.("feishu_urgent: Registered feishu_urgent tool");
64
+ }
@@ -0,0 +1,25 @@
1
+ import { Type, type Static } from "@sinclair/typebox";
2
+
3
+ const URGENT_TYPE_VALUES = ["app", "sms", "phone"] as const;
4
+
5
+ export const FeishuUrgentSchema = Type.Object({
6
+ message_id: Type.String({
7
+ description: "Message ID to send urgent notification for (e.g. om_xxx). The message must already be sent.",
8
+ }),
9
+ user_ids: Type.Array(Type.String(), {
10
+ description:
11
+ "List of open_id values to buzz. Recipients must be members of the chat where the message was sent.",
12
+ minItems: 1,
13
+ }),
14
+ urgent_type: Type.Optional(
15
+ Type.Unsafe<(typeof URGENT_TYPE_VALUES)[number]>({
16
+ type: "string",
17
+ enum: [...URGENT_TYPE_VALUES],
18
+ description:
19
+ "Urgency delivery method: app (in-app buzz, default), sms (SMS push), phone (voice call). Note: sms and phone may incur cost on the tenant.",
20
+ default: "app",
21
+ }),
22
+ ),
23
+ });
24
+
25
+ export type FeishuUrgentParams = Static<typeof FeishuUrgentSchema>;
@@ -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>;