@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.
- package/README.md +82 -2
- package/index.ts +7 -2
- package/package.json +5 -4
- package/skills/feishu-chat/SKILL.md +139 -0
- package/skills/feishu-urgent/SKILL.md +107 -0
- package/src/bot.ts +391 -45
- package/src/channel.ts +2 -0
- package/src/chat-tools/actions.ts +502 -0
- package/src/chat-tools/common.ts +13 -0
- package/src/chat-tools/index.ts +2 -0
- package/src/chat-tools/register.ts +72 -0
- package/src/chat-tools/schemas.ts +61 -0
- package/src/client.ts +13 -0
- package/src/config-schema.ts +6 -0
- package/src/doc-tools/actions.ts +63 -14
- package/src/doc-tools/schemas.ts +41 -77
- package/src/doc-write-service.ts +19 -0
- package/src/drive-tools/actions.ts +75 -27
- package/src/drive-tools/schemas.ts +45 -58
- package/src/media-duration.ts +190 -0
- package/src/media.ts +50 -27
- package/src/mention.ts +1 -1
- package/src/monitor.ts +32 -8
- package/src/outbound.ts +2 -2
- package/src/perm-tools/actions.ts +27 -3
- package/src/perm-tools/schemas.ts +39 -45
- package/src/policy.ts +7 -3
- package/src/reply-dispatcher.ts +97 -13
- package/src/send.ts +360 -42
- package/src/streaming-card.ts +32 -4
- package/src/task-tools/actions.ts +145 -10
- package/src/task-tools/register.ts +56 -1
- package/src/task-tools/schemas.ts +23 -18
- package/src/tools-common/tool-context.ts +1 -0
- package/src/tools-config.ts +9 -2
- package/src/types.ts +8 -1
- package/src/typing.ts +115 -5
- package/src/urgent-tools/actions.ts +84 -0
- package/src/urgent-tools/index.ts +3 -0
- package/src/urgent-tools/register.ts +64 -0
- package/src/urgent-tools/schemas.ts +25 -0
- package/src/wiki-tools/actions.ts +24 -6
- 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,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(
|
|
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(
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Type.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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>;
|