@openclaw/feishu 2026.2.2 → 2026.2.9
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/index.ts +49 -1
- package/openclaw.plugin.json +1 -0
- package/package.json +9 -5
- package/skills/feishu-doc/SKILL.md +105 -0
- package/skills/feishu-doc/references/block-types.md +103 -0
- package/skills/feishu-drive/SKILL.md +97 -0
- package/skills/feishu-perm/SKILL.md +119 -0
- package/skills/feishu-wiki/SKILL.md +111 -0
- package/src/accounts.ts +144 -0
- package/src/bitable.ts +461 -0
- package/src/bot.ts +871 -0
- package/src/channel.ts +284 -201
- package/src/client.ts +118 -0
- package/src/config-schema.ts +152 -26
- package/src/directory.ts +177 -0
- package/src/doc-schema.ts +47 -0
- package/src/docx.ts +521 -0
- package/src/drive-schema.ts +46 -0
- package/src/drive.ts +227 -0
- package/src/media.ts +527 -0
- package/src/mention.ts +126 -0
- package/src/monitor.ts +190 -0
- package/src/onboarding.ts +281 -200
- package/src/outbound.ts +55 -0
- package/src/perm-schema.ts +52 -0
- package/src/perm.ts +173 -0
- package/src/policy.ts +104 -0
- package/src/probe.ts +44 -0
- package/src/reactions.ts +160 -0
- package/src/reply-dispatcher.ts +179 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +358 -0
- package/src/targets.ts +78 -0
- package/src/tools-config.ts +21 -0
- package/src/types.ts +75 -0
- package/src/typing.ts +80 -0
- package/src/wiki-schema.ts +55 -0
- package/src/wiki.ts +232 -0
package/src/config-schema.ts
CHANGED
|
@@ -1,46 +1,172 @@
|
|
|
1
|
-
import { MarkdownConfigSchema, ToolPolicySchema } from "openclaw/plugin-sdk";
|
|
2
1
|
import { z } from "zod";
|
|
2
|
+
export { z };
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const DmPolicySchema = z.enum(["open", "pairing", "allowlist"]);
|
|
5
|
+
const GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]);
|
|
6
|
+
const FeishuDomainSchema = z.union([
|
|
7
|
+
z.enum(["feishu", "lark"]),
|
|
8
|
+
z.string().url().startsWith("https://"),
|
|
9
|
+
]);
|
|
10
|
+
const FeishuConnectionModeSchema = z.enum(["websocket", "webhook"]);
|
|
6
11
|
|
|
7
|
-
const
|
|
12
|
+
const ToolPolicySchema = z
|
|
13
|
+
.object({
|
|
14
|
+
allow: z.array(z.string()).optional(),
|
|
15
|
+
deny: z.array(z.string()).optional(),
|
|
16
|
+
})
|
|
17
|
+
.strict()
|
|
18
|
+
.optional();
|
|
19
|
+
|
|
20
|
+
const DmConfigSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
enabled: z.boolean().optional(),
|
|
23
|
+
systemPrompt: z.string().optional(),
|
|
24
|
+
})
|
|
25
|
+
.strict()
|
|
26
|
+
.optional();
|
|
27
|
+
|
|
28
|
+
const MarkdownConfigSchema = z
|
|
29
|
+
.object({
|
|
30
|
+
mode: z.enum(["native", "escape", "strip"]).optional(),
|
|
31
|
+
tableMode: z.enum(["native", "ascii", "simple"]).optional(),
|
|
32
|
+
})
|
|
33
|
+
.strict()
|
|
34
|
+
.optional();
|
|
35
|
+
|
|
36
|
+
// Message render mode: auto (default) = detect markdown, raw = plain text, card = always card
|
|
37
|
+
const RenderModeSchema = z.enum(["auto", "raw", "card"]).optional();
|
|
38
|
+
|
|
39
|
+
const BlockStreamingCoalesceSchema = z
|
|
8
40
|
.object({
|
|
9
41
|
enabled: z.boolean().optional(),
|
|
42
|
+
minDelayMs: z.number().int().positive().optional(),
|
|
43
|
+
maxDelayMs: z.number().int().positive().optional(),
|
|
44
|
+
})
|
|
45
|
+
.strict()
|
|
46
|
+
.optional();
|
|
47
|
+
|
|
48
|
+
const ChannelHeartbeatVisibilitySchema = z
|
|
49
|
+
.object({
|
|
50
|
+
visibility: z.enum(["visible", "hidden"]).optional(),
|
|
51
|
+
intervalMs: z.number().int().positive().optional(),
|
|
52
|
+
})
|
|
53
|
+
.strict()
|
|
54
|
+
.optional();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Feishu tools configuration.
|
|
58
|
+
* Controls which tool categories are enabled.
|
|
59
|
+
*
|
|
60
|
+
* Dependencies:
|
|
61
|
+
* - wiki requires doc (wiki content is edited via doc tools)
|
|
62
|
+
* - perm can work independently but is typically used with drive
|
|
63
|
+
*/
|
|
64
|
+
const FeishuToolsConfigSchema = z
|
|
65
|
+
.object({
|
|
66
|
+
doc: z.boolean().optional(), // Document operations (default: true)
|
|
67
|
+
wiki: z.boolean().optional(), // Knowledge base operations (default: true, requires doc)
|
|
68
|
+
drive: z.boolean().optional(), // Cloud storage operations (default: true)
|
|
69
|
+
perm: z.boolean().optional(), // Permission management (default: false, sensitive)
|
|
70
|
+
scopes: z.boolean().optional(), // App scopes diagnostic (default: true)
|
|
71
|
+
})
|
|
72
|
+
.strict()
|
|
73
|
+
.optional();
|
|
74
|
+
|
|
75
|
+
export const FeishuGroupSchema = z
|
|
76
|
+
.object({
|
|
10
77
|
requireMention: z.boolean().optional(),
|
|
11
|
-
allowFrom: z.array(allowFromEntry).optional(),
|
|
12
78
|
tools: ToolPolicySchema,
|
|
13
|
-
toolsBySender: toolsBySenderSchema,
|
|
14
|
-
systemPrompt: z.string().optional(),
|
|
15
79
|
skills: z.array(z.string()).optional(),
|
|
80
|
+
enabled: z.boolean().optional(),
|
|
81
|
+
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
82
|
+
systemPrompt: z.string().optional(),
|
|
16
83
|
})
|
|
17
84
|
.strict();
|
|
18
85
|
|
|
19
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Per-account configuration.
|
|
88
|
+
* All fields are optional - missing fields inherit from top-level config.
|
|
89
|
+
*/
|
|
90
|
+
export const FeishuAccountConfigSchema = z
|
|
20
91
|
.object({
|
|
21
|
-
name: z.string().optional(),
|
|
22
92
|
enabled: z.boolean().optional(),
|
|
93
|
+
name: z.string().optional(), // Display name for this account
|
|
23
94
|
appId: z.string().optional(),
|
|
24
95
|
appSecret: z.string().optional(),
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
96
|
+
encryptKey: z.string().optional(),
|
|
97
|
+
verificationToken: z.string().optional(),
|
|
98
|
+
domain: FeishuDomainSchema.optional(),
|
|
99
|
+
connectionMode: FeishuConnectionModeSchema.optional(),
|
|
100
|
+
webhookPath: z.string().optional(),
|
|
101
|
+
webhookPort: z.number().int().positive().optional(),
|
|
102
|
+
capabilities: z.array(z.string()).optional(),
|
|
28
103
|
markdown: MarkdownConfigSchema,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
allowFrom: z.array(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
textChunkLimit: z.number().optional(),
|
|
36
|
-
chunkMode: z.enum(["length", "newline"]).optional(),
|
|
37
|
-
blockStreaming: z.boolean().optional(),
|
|
38
|
-
streaming: z.boolean().optional(),
|
|
39
|
-
mediaMaxMb: z.number().optional(),
|
|
104
|
+
configWrites: z.boolean().optional(),
|
|
105
|
+
dmPolicy: DmPolicySchema.optional(),
|
|
106
|
+
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
107
|
+
groupPolicy: GroupPolicySchema.optional(),
|
|
108
|
+
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
109
|
+
requireMention: z.boolean().optional(),
|
|
40
110
|
groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
|
|
111
|
+
historyLimit: z.number().int().min(0).optional(),
|
|
112
|
+
dmHistoryLimit: z.number().int().min(0).optional(),
|
|
113
|
+
dms: z.record(z.string(), DmConfigSchema).optional(),
|
|
114
|
+
textChunkLimit: z.number().int().positive().optional(),
|
|
115
|
+
chunkMode: z.enum(["length", "newline"]).optional(),
|
|
116
|
+
blockStreamingCoalesce: BlockStreamingCoalesceSchema,
|
|
117
|
+
mediaMaxMb: z.number().positive().optional(),
|
|
118
|
+
heartbeat: ChannelHeartbeatVisibilitySchema,
|
|
119
|
+
renderMode: RenderModeSchema,
|
|
120
|
+
tools: FeishuToolsConfigSchema,
|
|
41
121
|
})
|
|
42
122
|
.strict();
|
|
43
123
|
|
|
44
|
-
export const FeishuConfigSchema =
|
|
45
|
-
|
|
46
|
-
|
|
124
|
+
export const FeishuConfigSchema = z
|
|
125
|
+
.object({
|
|
126
|
+
enabled: z.boolean().optional(),
|
|
127
|
+
// Top-level credentials (backward compatible for single-account mode)
|
|
128
|
+
appId: z.string().optional(),
|
|
129
|
+
appSecret: z.string().optional(),
|
|
130
|
+
encryptKey: z.string().optional(),
|
|
131
|
+
verificationToken: z.string().optional(),
|
|
132
|
+
domain: FeishuDomainSchema.optional().default("feishu"),
|
|
133
|
+
connectionMode: FeishuConnectionModeSchema.optional().default("websocket"),
|
|
134
|
+
webhookPath: z.string().optional().default("/feishu/events"),
|
|
135
|
+
webhookPort: z.number().int().positive().optional(),
|
|
136
|
+
capabilities: z.array(z.string()).optional(),
|
|
137
|
+
markdown: MarkdownConfigSchema,
|
|
138
|
+
configWrites: z.boolean().optional(),
|
|
139
|
+
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
|
140
|
+
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
141
|
+
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
|
142
|
+
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
143
|
+
requireMention: z.boolean().optional().default(true),
|
|
144
|
+
groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
|
|
145
|
+
historyLimit: z.number().int().min(0).optional(),
|
|
146
|
+
dmHistoryLimit: z.number().int().min(0).optional(),
|
|
147
|
+
dms: z.record(z.string(), DmConfigSchema).optional(),
|
|
148
|
+
textChunkLimit: z.number().int().positive().optional(),
|
|
149
|
+
chunkMode: z.enum(["length", "newline"]).optional(),
|
|
150
|
+
blockStreamingCoalesce: BlockStreamingCoalesceSchema,
|
|
151
|
+
mediaMaxMb: z.number().positive().optional(),
|
|
152
|
+
heartbeat: ChannelHeartbeatVisibilitySchema,
|
|
153
|
+
renderMode: RenderModeSchema, // raw = plain text (default), card = interactive card with markdown
|
|
154
|
+
tools: FeishuToolsConfigSchema,
|
|
155
|
+
// Multi-account configuration
|
|
156
|
+
accounts: z.record(z.string(), FeishuAccountConfigSchema.optional()).optional(),
|
|
157
|
+
})
|
|
158
|
+
.strict()
|
|
159
|
+
.superRefine((value, ctx) => {
|
|
160
|
+
if (value.dmPolicy === "open") {
|
|
161
|
+
const allowFrom = value.allowFrom ?? [];
|
|
162
|
+
const hasWildcard = allowFrom.some((entry) => String(entry).trim() === "*");
|
|
163
|
+
if (!hasWildcard) {
|
|
164
|
+
ctx.addIssue({
|
|
165
|
+
code: z.ZodIssueCode.custom,
|
|
166
|
+
path: ["allowFrom"],
|
|
167
|
+
message:
|
|
168
|
+
'channels.feishu.dmPolicy="open" requires channels.feishu.allowFrom to include "*"',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
package/src/directory.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import { resolveFeishuAccount } from "./accounts.js";
|
|
3
|
+
import { createFeishuClient } from "./client.js";
|
|
4
|
+
import { normalizeFeishuTarget } from "./targets.js";
|
|
5
|
+
|
|
6
|
+
export type FeishuDirectoryPeer = {
|
|
7
|
+
kind: "user";
|
|
8
|
+
id: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type FeishuDirectoryGroup = {
|
|
13
|
+
kind: "group";
|
|
14
|
+
id: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export async function listFeishuDirectoryPeers(params: {
|
|
19
|
+
cfg: ClawdbotConfig;
|
|
20
|
+
query?: string;
|
|
21
|
+
limit?: number;
|
|
22
|
+
accountId?: string;
|
|
23
|
+
}): Promise<FeishuDirectoryPeer[]> {
|
|
24
|
+
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
25
|
+
const feishuCfg = account.config;
|
|
26
|
+
const q = params.query?.trim().toLowerCase() || "";
|
|
27
|
+
const ids = new Set<string>();
|
|
28
|
+
|
|
29
|
+
for (const entry of feishuCfg?.allowFrom ?? []) {
|
|
30
|
+
const trimmed = String(entry).trim();
|
|
31
|
+
if (trimmed && trimmed !== "*") {
|
|
32
|
+
ids.add(trimmed);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const userId of Object.keys(feishuCfg?.dms ?? {})) {
|
|
37
|
+
const trimmed = userId.trim();
|
|
38
|
+
if (trimmed) {
|
|
39
|
+
ids.add(trimmed);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return Array.from(ids)
|
|
44
|
+
.map((raw) => raw.trim())
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.map((raw) => normalizeFeishuTarget(raw) ?? raw)
|
|
47
|
+
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
|
|
48
|
+
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
|
|
49
|
+
.map((id) => ({ kind: "user" as const, id }));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function listFeishuDirectoryGroups(params: {
|
|
53
|
+
cfg: ClawdbotConfig;
|
|
54
|
+
query?: string;
|
|
55
|
+
limit?: number;
|
|
56
|
+
accountId?: string;
|
|
57
|
+
}): Promise<FeishuDirectoryGroup[]> {
|
|
58
|
+
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
59
|
+
const feishuCfg = account.config;
|
|
60
|
+
const q = params.query?.trim().toLowerCase() || "";
|
|
61
|
+
const ids = new Set<string>();
|
|
62
|
+
|
|
63
|
+
for (const groupId of Object.keys(feishuCfg?.groups ?? {})) {
|
|
64
|
+
const trimmed = groupId.trim();
|
|
65
|
+
if (trimmed && trimmed !== "*") {
|
|
66
|
+
ids.add(trimmed);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const entry of feishuCfg?.groupAllowFrom ?? []) {
|
|
71
|
+
const trimmed = String(entry).trim();
|
|
72
|
+
if (trimmed && trimmed !== "*") {
|
|
73
|
+
ids.add(trimmed);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return Array.from(ids)
|
|
78
|
+
.map((raw) => raw.trim())
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
|
|
81
|
+
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
|
|
82
|
+
.map((id) => ({ kind: "group" as const, id }));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function listFeishuDirectoryPeersLive(params: {
|
|
86
|
+
cfg: ClawdbotConfig;
|
|
87
|
+
query?: string;
|
|
88
|
+
limit?: number;
|
|
89
|
+
accountId?: string;
|
|
90
|
+
}): Promise<FeishuDirectoryPeer[]> {
|
|
91
|
+
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
92
|
+
if (!account.configured) {
|
|
93
|
+
return listFeishuDirectoryPeers(params);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const client = createFeishuClient(account);
|
|
98
|
+
const peers: FeishuDirectoryPeer[] = [];
|
|
99
|
+
const limit = params.limit ?? 50;
|
|
100
|
+
|
|
101
|
+
const response = await client.contact.user.list({
|
|
102
|
+
params: {
|
|
103
|
+
page_size: Math.min(limit, 50),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (response.code === 0 && response.data?.items) {
|
|
108
|
+
for (const user of response.data.items) {
|
|
109
|
+
if (user.open_id) {
|
|
110
|
+
const q = params.query?.trim().toLowerCase() || "";
|
|
111
|
+
const name = user.name || "";
|
|
112
|
+
if (!q || user.open_id.toLowerCase().includes(q) || name.toLowerCase().includes(q)) {
|
|
113
|
+
peers.push({
|
|
114
|
+
kind: "user",
|
|
115
|
+
id: user.open_id,
|
|
116
|
+
name: name || undefined,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (peers.length >= limit) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return peers;
|
|
127
|
+
} catch {
|
|
128
|
+
return listFeishuDirectoryPeers(params);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function listFeishuDirectoryGroupsLive(params: {
|
|
133
|
+
cfg: ClawdbotConfig;
|
|
134
|
+
query?: string;
|
|
135
|
+
limit?: number;
|
|
136
|
+
accountId?: string;
|
|
137
|
+
}): Promise<FeishuDirectoryGroup[]> {
|
|
138
|
+
const account = resolveFeishuAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
139
|
+
if (!account.configured) {
|
|
140
|
+
return listFeishuDirectoryGroups(params);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const client = createFeishuClient(account);
|
|
145
|
+
const groups: FeishuDirectoryGroup[] = [];
|
|
146
|
+
const limit = params.limit ?? 50;
|
|
147
|
+
|
|
148
|
+
const response = await client.im.chat.list({
|
|
149
|
+
params: {
|
|
150
|
+
page_size: Math.min(limit, 100),
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (response.code === 0 && response.data?.items) {
|
|
155
|
+
for (const chat of response.data.items) {
|
|
156
|
+
if (chat.chat_id) {
|
|
157
|
+
const q = params.query?.trim().toLowerCase() || "";
|
|
158
|
+
const name = chat.name || "";
|
|
159
|
+
if (!q || chat.chat_id.toLowerCase().includes(q) || name.toLowerCase().includes(q)) {
|
|
160
|
+
groups.push({
|
|
161
|
+
kind: "group",
|
|
162
|
+
id: chat.chat_id,
|
|
163
|
+
name: name || undefined,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (groups.length >= limit) {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return groups;
|
|
174
|
+
} catch {
|
|
175
|
+
return listFeishuDirectoryGroups(params);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
export const FeishuDocSchema = Type.Union([
|
|
4
|
+
Type.Object({
|
|
5
|
+
action: Type.Literal("read"),
|
|
6
|
+
doc_token: Type.String({ description: "Document token (extract from URL /docx/XXX)" }),
|
|
7
|
+
}),
|
|
8
|
+
Type.Object({
|
|
9
|
+
action: Type.Literal("write"),
|
|
10
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
11
|
+
content: Type.String({
|
|
12
|
+
description: "Markdown content to write (replaces entire document content)",
|
|
13
|
+
}),
|
|
14
|
+
}),
|
|
15
|
+
Type.Object({
|
|
16
|
+
action: Type.Literal("append"),
|
|
17
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
18
|
+
content: Type.String({ description: "Markdown content to append to end of document" }),
|
|
19
|
+
}),
|
|
20
|
+
Type.Object({
|
|
21
|
+
action: Type.Literal("create"),
|
|
22
|
+
title: Type.String({ description: "Document title" }),
|
|
23
|
+
folder_token: Type.Optional(Type.String({ description: "Target folder token (optional)" })),
|
|
24
|
+
}),
|
|
25
|
+
Type.Object({
|
|
26
|
+
action: Type.Literal("list_blocks"),
|
|
27
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
28
|
+
}),
|
|
29
|
+
Type.Object({
|
|
30
|
+
action: Type.Literal("get_block"),
|
|
31
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
32
|
+
block_id: Type.String({ description: "Block ID (from list_blocks)" }),
|
|
33
|
+
}),
|
|
34
|
+
Type.Object({
|
|
35
|
+
action: Type.Literal("update_block"),
|
|
36
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
37
|
+
block_id: Type.String({ description: "Block ID (from list_blocks)" }),
|
|
38
|
+
content: Type.String({ description: "New text content" }),
|
|
39
|
+
}),
|
|
40
|
+
Type.Object({
|
|
41
|
+
action: Type.Literal("delete_block"),
|
|
42
|
+
doc_token: Type.String({ description: "Document token" }),
|
|
43
|
+
block_id: Type.String({ description: "Block ID" }),
|
|
44
|
+
}),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export type FeishuDocParams = Static<typeof FeishuDocSchema>;
|