@nextclaw/channel-plugin-feishu 0.2.12 → 0.2.14

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 (102) hide show
  1. package/README.md +3 -1
  2. package/index.ts +65 -0
  3. package/openclaw.plugin.json +3 -7
  4. package/package.json +33 -9
  5. package/skills/feishu-doc/SKILL.md +211 -0
  6. package/skills/feishu-doc/references/block-types.md +103 -0
  7. package/skills/feishu-drive/SKILL.md +97 -0
  8. package/skills/feishu-perm/SKILL.md +119 -0
  9. package/skills/feishu-wiki/SKILL.md +111 -0
  10. package/src/accounts.test.ts +371 -0
  11. package/src/accounts.ts +244 -0
  12. package/src/async.ts +62 -0
  13. package/src/bitable.ts +725 -0
  14. package/src/bot.card-action.test.ts +63 -0
  15. package/src/bot.checkBotMentioned.test.ts +193 -0
  16. package/src/bot.stripBotMention.test.ts +134 -0
  17. package/src/bot.test.ts +2107 -0
  18. package/src/bot.ts +1556 -0
  19. package/src/card-action.ts +79 -0
  20. package/src/channel.test.ts +48 -0
  21. package/src/channel.ts +369 -0
  22. package/src/chat-schema.ts +24 -0
  23. package/src/chat.test.ts +89 -0
  24. package/src/chat.ts +130 -0
  25. package/src/client.test.ts +324 -0
  26. package/src/client.ts +196 -0
  27. package/src/config-schema.test.ts +247 -0
  28. package/src/config-schema.ts +306 -0
  29. package/src/dedup.ts +203 -0
  30. package/src/directory.test.ts +40 -0
  31. package/src/directory.ts +156 -0
  32. package/src/doc-schema.ts +182 -0
  33. package/src/docx-batch-insert.test.ts +90 -0
  34. package/src/docx-batch-insert.ts +187 -0
  35. package/src/docx-color-text.ts +149 -0
  36. package/src/docx-table-ops.ts +298 -0
  37. package/src/docx.account-selection.test.ts +70 -0
  38. package/src/docx.test.ts +445 -0
  39. package/src/docx.ts +1460 -0
  40. package/src/drive-schema.ts +46 -0
  41. package/src/drive.ts +228 -0
  42. package/src/dynamic-agent.ts +131 -0
  43. package/src/external-keys.test.ts +20 -0
  44. package/src/external-keys.ts +19 -0
  45. package/src/feishu-command-handler.ts +59 -0
  46. package/src/media.test.ts +523 -0
  47. package/src/media.ts +484 -0
  48. package/src/mention.ts +133 -0
  49. package/src/monitor.account.ts +562 -0
  50. package/src/monitor.reaction.test.ts +653 -0
  51. package/src/monitor.startup.test.ts +190 -0
  52. package/src/monitor.startup.ts +64 -0
  53. package/src/monitor.state.defaults.test.ts +46 -0
  54. package/src/monitor.state.ts +155 -0
  55. package/src/monitor.test-mocks.ts +45 -0
  56. package/src/monitor.transport.ts +264 -0
  57. package/src/monitor.ts +95 -0
  58. package/src/monitor.webhook-e2e.test.ts +214 -0
  59. package/src/monitor.webhook-security.test.ts +142 -0
  60. package/src/monitor.webhook.test-helpers.ts +98 -0
  61. package/src/onboarding.status.test.ts +25 -0
  62. package/src/onboarding.test.ts +143 -0
  63. package/src/onboarding.ts +489 -0
  64. package/src/outbound.test.ts +356 -0
  65. package/src/outbound.ts +176 -0
  66. package/src/perm-schema.ts +52 -0
  67. package/src/perm.ts +176 -0
  68. package/src/policy.test.ts +154 -0
  69. package/src/policy.ts +123 -0
  70. package/src/post.test.ts +105 -0
  71. package/src/post.ts +274 -0
  72. package/src/probe.test.ts +270 -0
  73. package/src/probe.ts +156 -0
  74. package/src/reactions.ts +153 -0
  75. package/src/reply-dispatcher.test.ts +513 -0
  76. package/src/reply-dispatcher.ts +397 -0
  77. package/src/runtime.ts +6 -0
  78. package/src/secret-input.ts +13 -0
  79. package/src/send-message.ts +71 -0
  80. package/src/send-result.ts +29 -0
  81. package/src/send-target.test.ts +74 -0
  82. package/src/send-target.ts +29 -0
  83. package/src/send.reply-fallback.test.ts +189 -0
  84. package/src/send.test.ts +168 -0
  85. package/src/send.ts +481 -0
  86. package/src/streaming-card.test.ts +54 -0
  87. package/src/streaming-card.ts +374 -0
  88. package/src/targets.test.ts +70 -0
  89. package/src/targets.ts +107 -0
  90. package/src/tool-account-routing.test.ts +129 -0
  91. package/src/tool-account.ts +70 -0
  92. package/src/tool-factory-test-harness.ts +76 -0
  93. package/src/tool-result.test.ts +32 -0
  94. package/src/tool-result.ts +14 -0
  95. package/src/tools-config.test.ts +21 -0
  96. package/src/tools-config.ts +22 -0
  97. package/src/types.ts +103 -0
  98. package/src/typing.test.ts +144 -0
  99. package/src/typing.ts +210 -0
  100. package/src/wiki-schema.ts +55 -0
  101. package/src/wiki.ts +233 -0
  102. package/index.js +0 -27
@@ -0,0 +1,55 @@
1
+ import { Type, type Static } from "@sinclair/typebox";
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
+ ]);
54
+
55
+ export type FeishuWikiParams = Static<typeof FeishuWikiSchema>;
package/src/wiki.ts ADDED
@@ -0,0 +1,233 @@
1
+ import type * as Lark from "@larksuiteoapi/node-sdk";
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
3
+ import { listEnabledFeishuAccounts } from "./accounts.js";
4
+ import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
5
+ import {
6
+ jsonToolResult,
7
+ toolExecutionErrorResult,
8
+ unknownToolActionResult,
9
+ } from "./tool-result.js";
10
+ import { FeishuWikiSchema, type FeishuWikiParams } from "./wiki-schema.js";
11
+
12
+ type ObjType = "doc" | "sheet" | "mindnote" | "bitable" | "file" | "docx" | "slides";
13
+
14
+ // ============ Actions ============
15
+
16
+ const WIKI_ACCESS_HINT =
17
+ "To grant wiki access: Open wiki space → Settings → Members → Add the bot. " +
18
+ "See: https://open.feishu.cn/document/server-docs/docs/wiki-v2/wiki-qa#a40ad4ca";
19
+
20
+ async function listSpaces(client: Lark.Client) {
21
+ const res = await client.wiki.space.list({});
22
+ if (res.code !== 0) {
23
+ throw new Error(res.msg);
24
+ }
25
+
26
+ const spaces =
27
+ res.data?.items?.map((s) => ({
28
+ space_id: s.space_id,
29
+ name: s.name,
30
+ description: s.description,
31
+ visibility: s.visibility,
32
+ })) ?? [];
33
+
34
+ return {
35
+ spaces,
36
+ ...(spaces.length === 0 && { hint: WIKI_ACCESS_HINT }),
37
+ };
38
+ }
39
+
40
+ async function listNodes(client: Lark.Client, spaceId: string, parentNodeToken?: string) {
41
+ const res = await client.wiki.spaceNode.list({
42
+ path: { space_id: spaceId },
43
+ params: { parent_node_token: parentNodeToken },
44
+ });
45
+ if (res.code !== 0) {
46
+ throw new Error(res.msg);
47
+ }
48
+
49
+ return {
50
+ nodes:
51
+ res.data?.items?.map((n) => ({
52
+ node_token: n.node_token,
53
+ obj_token: n.obj_token,
54
+ obj_type: n.obj_type,
55
+ title: n.title,
56
+ has_child: n.has_child,
57
+ })) ?? [],
58
+ };
59
+ }
60
+
61
+ async function getNode(client: Lark.Client, token: string) {
62
+ const res = await client.wiki.space.getNode({
63
+ params: { token },
64
+ });
65
+ if (res.code !== 0) {
66
+ throw new Error(res.msg);
67
+ }
68
+
69
+ const node = res.data?.node;
70
+ return {
71
+ node_token: node?.node_token,
72
+ space_id: node?.space_id,
73
+ obj_token: node?.obj_token,
74
+ obj_type: node?.obj_type,
75
+ title: node?.title,
76
+ parent_node_token: node?.parent_node_token,
77
+ has_child: node?.has_child,
78
+ creator: node?.creator,
79
+ create_time: node?.node_create_time,
80
+ };
81
+ }
82
+
83
+ async function createNode(
84
+ client: Lark.Client,
85
+ spaceId: string,
86
+ title: string,
87
+ objType?: string,
88
+ parentNodeToken?: string,
89
+ ) {
90
+ const res = await client.wiki.spaceNode.create({
91
+ path: { space_id: spaceId },
92
+ data: {
93
+ obj_type: (objType as ObjType) || "docx",
94
+ node_type: "origin" as const,
95
+ title,
96
+ parent_node_token: parentNodeToken,
97
+ },
98
+ });
99
+ if (res.code !== 0) {
100
+ throw new Error(res.msg);
101
+ }
102
+
103
+ const node = res.data?.node;
104
+ return {
105
+ node_token: node?.node_token,
106
+ obj_token: node?.obj_token,
107
+ obj_type: node?.obj_type,
108
+ title: node?.title,
109
+ };
110
+ }
111
+
112
+ async function moveNode(
113
+ client: Lark.Client,
114
+ spaceId: string,
115
+ nodeToken: string,
116
+ targetSpaceId?: string,
117
+ targetParentToken?: string,
118
+ ) {
119
+ const res = await client.wiki.spaceNode.move({
120
+ path: { space_id: spaceId, node_token: nodeToken },
121
+ data: {
122
+ target_space_id: targetSpaceId || spaceId,
123
+ target_parent_token: targetParentToken,
124
+ },
125
+ });
126
+ if (res.code !== 0) {
127
+ throw new Error(res.msg);
128
+ }
129
+
130
+ return {
131
+ success: true,
132
+ node_token: res.data?.node?.node_token,
133
+ };
134
+ }
135
+
136
+ async function renameNode(client: Lark.Client, spaceId: string, nodeToken: string, title: string) {
137
+ const res = await client.wiki.spaceNode.updateTitle({
138
+ path: { space_id: spaceId, node_token: nodeToken },
139
+ data: { title },
140
+ });
141
+ if (res.code !== 0) {
142
+ throw new Error(res.msg);
143
+ }
144
+
145
+ return {
146
+ success: true,
147
+ node_token: nodeToken,
148
+ title,
149
+ };
150
+ }
151
+
152
+ // ============ Tool Registration ============
153
+
154
+ export function registerFeishuWikiTools(api: OpenClawPluginApi) {
155
+ if (!api.config) {
156
+ api.logger.debug?.("feishu_wiki: No config available, skipping wiki tools");
157
+ return;
158
+ }
159
+
160
+ const accounts = listEnabledFeishuAccounts(api.config);
161
+ if (accounts.length === 0) {
162
+ api.logger.debug?.("feishu_wiki: No Feishu accounts configured, skipping wiki tools");
163
+ return;
164
+ }
165
+
166
+ const toolsCfg = resolveAnyEnabledFeishuToolsConfig(accounts);
167
+ if (!toolsCfg.wiki) {
168
+ api.logger.debug?.("feishu_wiki: wiki tool disabled in config");
169
+ return;
170
+ }
171
+
172
+ type FeishuWikiExecuteParams = FeishuWikiParams & { accountId?: string };
173
+
174
+ api.registerTool(
175
+ (ctx) => {
176
+ const defaultAccountId = ctx.agentAccountId;
177
+ return {
178
+ name: "feishu_wiki",
179
+ label: "Feishu Wiki",
180
+ description:
181
+ "Feishu knowledge base operations. Actions: spaces, nodes, get, create, move, rename",
182
+ parameters: FeishuWikiSchema,
183
+ async execute(_toolCallId, params) {
184
+ const p = params as FeishuWikiExecuteParams;
185
+ try {
186
+ const client = createFeishuToolClient({
187
+ api,
188
+ executeParams: p,
189
+ defaultAccountId,
190
+ });
191
+ switch (p.action) {
192
+ case "spaces":
193
+ return jsonToolResult(await listSpaces(client));
194
+ case "nodes":
195
+ return jsonToolResult(await listNodes(client, p.space_id, p.parent_node_token));
196
+ case "get":
197
+ return jsonToolResult(await getNode(client, p.token));
198
+ case "search":
199
+ return jsonToolResult({
200
+ error:
201
+ "Search is not available. Use feishu_wiki with action: 'nodes' to browse or action: 'get' to lookup by token.",
202
+ });
203
+ case "create":
204
+ return jsonToolResult(
205
+ await createNode(client, p.space_id, p.title, p.obj_type, p.parent_node_token),
206
+ );
207
+ case "move":
208
+ return jsonToolResult(
209
+ await moveNode(
210
+ client,
211
+ p.space_id,
212
+ p.node_token,
213
+ p.target_space_id,
214
+ p.target_parent_token,
215
+ ),
216
+ );
217
+ case "rename":
218
+ return jsonToolResult(await renameNode(client, p.space_id, p.node_token, p.title));
219
+ default:
220
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback
221
+ return unknownToolActionResult((p as { action?: unknown }).action);
222
+ }
223
+ } catch (err) {
224
+ return toolExecutionErrorResult(err);
225
+ }
226
+ },
227
+ };
228
+ },
229
+ { name: "feishu_wiki" },
230
+ );
231
+
232
+ api.logger.info?.(`feishu_wiki: Registered feishu_wiki tool`);
233
+ }
package/index.js DELETED
@@ -1,27 +0,0 @@
1
- import { resolveBuiltinChannelRuntime } from "@nextclaw/channel-runtime";
2
-
3
- const runtime = resolveBuiltinChannelRuntime("feishu");
4
-
5
- const plugin = {
6
- id: "builtin-channel-feishu",
7
- name: "Builtin Feishu Channel",
8
- description: "Builtin NextClaw channel plugin for feishu",
9
- configSchema: {
10
- type: "object",
11
- additionalProperties: false,
12
- properties: {}
13
- },
14
- register(api) {
15
- api.registerChannel({
16
- plugin: {
17
- id: "feishu",
18
- nextclaw: {
19
- isEnabled: runtime.isEnabled,
20
- createChannel: runtime.createChannel
21
- }
22
- }
23
- });
24
- }
25
- };
26
-
27
- export default plugin;