@openclaw/feishu 2026.3.13 → 2026.5.2-beta.1
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/api.ts +31 -0
- package/channel-entry.ts +20 -0
- package/channel-plugin-api.ts +1 -0
- package/contract-api.ts +16 -0
- package/index.ts +70 -53
- package/openclaw.plugin.json +1827 -4
- package/package.json +32 -7
- package/runtime-api.ts +55 -0
- package/secret-contract-api.ts +5 -0
- package/security-contract-api.ts +1 -0
- package/session-key-api.ts +1 -0
- package/setup-api.ts +3 -0
- package/setup-entry.test.ts +14 -0
- package/setup-entry.ts +13 -0
- package/src/accounts.test.ts +95 -7
- package/src/accounts.ts +199 -117
- package/src/app-registration.ts +331 -0
- package/src/approval-auth.test.ts +24 -0
- package/src/approval-auth.ts +25 -0
- package/src/async.test.ts +35 -0
- package/src/async.ts +43 -1
- package/src/audio-preflight.runtime.ts +9 -0
- package/src/bitable.test.ts +131 -0
- package/src/bitable.ts +59 -22
- package/src/bot-content.ts +474 -0
- package/src/bot-group-name.test.ts +108 -0
- package/src/bot-runtime-api.ts +12 -0
- package/src/bot-sender-name.ts +125 -0
- package/src/bot.broadcast.test.ts +463 -0
- package/src/bot.card-action.test.ts +519 -5
- package/src/bot.checkBotMentioned.test.ts +92 -20
- package/src/bot.helpers.test.ts +118 -0
- package/src/bot.stripBotMention.test.ts +13 -21
- package/src/bot.test.ts +1334 -401
- package/src/bot.ts +778 -775
- package/src/card-action.ts +408 -40
- package/src/card-interaction.test.ts +129 -0
- package/src/card-interaction.ts +159 -0
- package/src/card-test-helpers.ts +47 -0
- package/src/card-ux-approval.ts +65 -0
- package/src/card-ux-launcher.test.ts +99 -0
- package/src/card-ux-launcher.ts +121 -0
- package/src/card-ux-shared.ts +33 -0
- package/src/channel-runtime-api.ts +16 -0
- package/src/channel.runtime.ts +47 -0
- package/src/channel.test.ts +914 -3
- package/src/channel.ts +1253 -309
- package/src/chat-schema.ts +5 -4
- package/src/chat.test.ts +135 -28
- package/src/chat.ts +68 -10
- package/src/client.test.ts +212 -103
- package/src/client.ts +115 -21
- package/src/comment-dispatcher-runtime-api.ts +6 -0
- package/src/comment-dispatcher.test.ts +169 -0
- package/src/comment-dispatcher.ts +107 -0
- package/src/comment-handler-runtime-api.ts +3 -0
- package/src/comment-handler.test.ts +486 -0
- package/src/comment-handler.ts +309 -0
- package/src/comment-reaction.test.ts +166 -0
- package/src/comment-reaction.ts +259 -0
- package/src/comment-shared.test.ts +182 -0
- package/src/comment-shared.ts +406 -0
- package/src/comment-target.ts +44 -0
- package/src/config-schema.test.ts +63 -1
- package/src/config-schema.ts +31 -4
- package/src/conversation-id.test.ts +18 -0
- package/src/conversation-id.ts +199 -0
- package/src/dedup-runtime-api.ts +1 -0
- package/src/dedup.ts +33 -95
- package/src/directory.static.ts +61 -0
- package/src/directory.test.ts +116 -20
- package/src/directory.ts +60 -92
- package/src/doc-schema.ts +1 -1
- package/src/docx-batch-insert.test.ts +39 -38
- package/src/docx-batch-insert.ts +55 -19
- package/src/docx-color-text.ts +9 -4
- package/src/docx-table-ops.test.ts +53 -0
- package/src/docx-table-ops.ts +52 -34
- package/src/docx-types.ts +38 -0
- package/src/docx.account-selection.test.ts +12 -3
- package/src/docx.test.ts +314 -74
- package/src/docx.ts +278 -122
- package/src/drive-schema.ts +47 -1
- package/src/drive.test.ts +1219 -0
- package/src/drive.ts +614 -13
- package/src/dynamic-agent.ts +10 -4
- package/src/event-types.ts +45 -0
- package/src/external-keys.ts +1 -1
- package/src/lifecycle.test-support.ts +220 -0
- package/src/media.test.ts +403 -26
- package/src/media.ts +509 -132
- package/src/mention-target.types.ts +5 -0
- package/src/mention.ts +32 -51
- package/src/message-action-contract.ts +13 -0
- package/src/monitor-state-runtime-api.ts +7 -0
- package/src/monitor-transport-runtime-api.ts +7 -0
- package/src/monitor.account.ts +218 -312
- package/src/monitor.acp-init-failure.lifecycle.test-support.ts +219 -0
- package/src/monitor.bot-identity.ts +86 -0
- package/src/monitor.bot-menu-handler.ts +165 -0
- package/src/monitor.bot-menu.lifecycle.test-support.ts +224 -0
- package/src/monitor.bot-menu.test.ts +178 -0
- package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +264 -0
- package/src/monitor.card-action.lifecycle.test-support.ts +373 -0
- package/src/monitor.cleanup.test.ts +376 -0
- package/src/monitor.comment-notice-handler.ts +105 -0
- package/src/monitor.comment.test.ts +937 -0
- package/src/monitor.comment.ts +1386 -0
- package/src/monitor.lifecycle.test.ts +4 -0
- package/src/monitor.message-handler.ts +339 -0
- package/src/monitor.reaction.lifecycle.test-support.ts +68 -0
- package/src/monitor.reaction.test.ts +108 -48
- package/src/monitor.startup.test.ts +11 -9
- package/src/monitor.startup.ts +26 -16
- package/src/monitor.state.ts +20 -5
- package/src/monitor.synthetic-error.ts +18 -0
- package/src/monitor.test-mocks.ts +2 -2
- package/src/monitor.transport.ts +220 -60
- package/src/monitor.ts +15 -10
- package/src/monitor.webhook-e2e.test.ts +65 -7
- package/src/monitor.webhook-security.test.ts +122 -0
- package/src/monitor.webhook.test-helpers.ts +44 -26
- package/src/outbound-runtime-api.ts +1 -0
- package/src/outbound.test.ts +616 -37
- package/src/outbound.ts +623 -81
- package/src/perm-schema.ts +1 -1
- package/src/perm.ts +1 -7
- package/src/pins.ts +108 -0
- package/src/policy.test.ts +297 -117
- package/src/policy.ts +142 -29
- package/src/post.ts +7 -6
- package/src/probe.test.ts +14 -9
- package/src/probe.ts +26 -16
- package/src/processing-claims.ts +59 -0
- package/src/qr-terminal.ts +1 -0
- package/src/reactions.ts +4 -34
- package/src/reasoning-preview.test.ts +59 -0
- package/src/reasoning-preview.ts +20 -0
- package/src/reply-dispatcher-runtime-api.ts +7 -0
- package/src/reply-dispatcher.test.ts +660 -29
- package/src/reply-dispatcher.ts +407 -154
- package/src/runtime.ts +6 -3
- package/src/secret-contract.ts +145 -0
- package/src/secret-input.ts +1 -13
- package/src/security-audit-shared.ts +69 -0
- package/src/security-audit.test.ts +61 -0
- package/src/security-audit.ts +1 -0
- package/src/send-result.ts +1 -1
- package/src/send-target.test.ts +9 -3
- package/src/send-target.ts +10 -4
- package/src/send.reply-fallback.test.ts +105 -2
- package/src/send.test.ts +386 -4
- package/src/send.ts +414 -95
- package/src/sequential-key.test.ts +72 -0
- package/src/sequential-key.ts +28 -0
- package/src/sequential-queue.test.ts +92 -0
- package/src/sequential-queue.ts +16 -0
- package/src/session-conversation.ts +42 -0
- package/src/session-route.ts +48 -0
- package/src/setup-core.ts +51 -0
- package/src/{onboarding.test.ts → setup-surface.test.ts} +52 -21
- package/src/setup-surface.ts +581 -0
- package/src/streaming-card.test.ts +138 -2
- package/src/streaming-card.ts +134 -18
- package/src/subagent-hooks.test.ts +603 -0
- package/src/subagent-hooks.ts +397 -0
- package/src/targets.ts +3 -13
- package/src/test-support/lifecycle-test-support.ts +453 -0
- package/src/thread-bindings.test.ts +143 -0
- package/src/thread-bindings.ts +330 -0
- package/src/tool-account-routing.test.ts +66 -8
- package/src/tool-account.test.ts +44 -0
- package/src/tool-account.ts +40 -17
- package/src/tool-factory-test-harness.ts +11 -8
- package/src/tool-result.ts +3 -1
- package/src/tools-config.ts +1 -1
- package/src/types.ts +16 -15
- package/src/typing.ts +10 -6
- package/src/wiki-schema.ts +1 -1
- package/src/wiki.ts +1 -7
- package/subagent-hooks-api.ts +31 -0
- package/tsconfig.json +16 -0
- package/src/feishu-command-handler.ts +0 -59
- package/src/onboarding.status.test.ts +0 -25
- package/src/onboarding.ts +0 -489
- package/src/send-message.ts +0 -71
- package/src/targets.test.ts +0 -70
package/src/docx.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { isAbsolute } from "node:path";
|
|
3
|
+
import { isAbsolute, resolve } from "node:path";
|
|
4
4
|
import { basename } from "node:path";
|
|
5
5
|
import type * as Lark from "@larksuiteoapi/node-sdk";
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
6
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
|
+
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
8
|
+
import { Type } from "typebox";
|
|
9
|
+
import type { OpenClawPluginApi } from "../runtime-api.js";
|
|
8
10
|
import { listEnabledFeishuAccounts } from "./accounts.js";
|
|
9
11
|
import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
|
|
10
12
|
import { BATCH_SIZE, insertBlocksInBatches } from "./docx-batch-insert.js";
|
|
@@ -17,6 +19,7 @@ import {
|
|
|
17
19
|
deleteTableColumns,
|
|
18
20
|
mergeTableCells,
|
|
19
21
|
} from "./docx-table-ops.js";
|
|
22
|
+
import type { FeishuDocxBlock, FeishuDocxBlockChild } from "./docx-types.js";
|
|
20
23
|
import { getFeishuRuntime } from "./runtime.js";
|
|
21
24
|
import {
|
|
22
25
|
createFeishuToolClient,
|
|
@@ -33,6 +36,24 @@ function json(data: unknown) {
|
|
|
33
36
|
};
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
function resolveDocToolLocalRoots(ctx: {
|
|
40
|
+
workspaceDir?: string;
|
|
41
|
+
fsPolicy?: { workspaceOnly: boolean };
|
|
42
|
+
}): string[] | undefined {
|
|
43
|
+
if (ctx.fsPolicy?.workspaceOnly !== true) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const workspaceDir = ctx.workspaceDir?.trim();
|
|
47
|
+
// Fail closed: workspace-only with no resolved workspace must not fall back
|
|
48
|
+
// to default managed roots.
|
|
49
|
+
if (!workspaceDir) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
// Workspace paths are expected to be absolute; resolve() normalizes any
|
|
53
|
+
// accidental relative input before passing roots to loadWebMedia.
|
|
54
|
+
return [resolve(workspaceDir)];
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
/** Extract image URLs from markdown content */
|
|
37
58
|
function extractImageUrls(markdown: string): string[] {
|
|
38
59
|
const regex = /!\[[^\]]*\]\(([^)]+)\)/g;
|
|
@@ -72,8 +93,10 @@ const BLOCK_TYPE_NAMES: Record<number, string> = {
|
|
|
72
93
|
const UNSUPPORTED_CREATE_TYPES = new Set([31, 32]);
|
|
73
94
|
|
|
74
95
|
/** Clean blocks for insertion (remove unsupported types and read-only fields) */
|
|
75
|
-
|
|
76
|
-
|
|
96
|
+
function cleanBlocksForInsert(blocks: FeishuDocxBlock[]): {
|
|
97
|
+
cleaned: FeishuDocxBlock[];
|
|
98
|
+
skipped: string[];
|
|
99
|
+
} {
|
|
77
100
|
const skipped: string[] = [];
|
|
78
101
|
const cleaned = blocks
|
|
79
102
|
.filter((block) => {
|
|
@@ -87,7 +110,7 @@ function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[
|
|
|
87
110
|
.map((block) => {
|
|
88
111
|
if (block.block_type === 31 && block.table?.merge_info) {
|
|
89
112
|
const { merge_info: _merge_info, ...tableRest } = block.table;
|
|
90
|
-
return {
|
|
113
|
+
return Object.assign({}, block, { table: tableRest });
|
|
91
114
|
}
|
|
92
115
|
return block;
|
|
93
116
|
});
|
|
@@ -113,23 +136,148 @@ async function convertMarkdown(client: Lark.Client, markdown: string) {
|
|
|
113
136
|
};
|
|
114
137
|
}
|
|
115
138
|
|
|
116
|
-
function
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
139
|
+
function normalizeChildIds(children: unknown): string[] {
|
|
140
|
+
if (Array.isArray(children)) {
|
|
141
|
+
return children.filter((child): child is string => typeof child === "string");
|
|
142
|
+
}
|
|
143
|
+
if (typeof children === "string") {
|
|
144
|
+
return [children];
|
|
145
|
+
}
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type DocxChildrenCreatePayload = NonNullable<
|
|
150
|
+
Parameters<Lark.Client["docx"]["documentBlockChildren"]["create"]>[0]
|
|
151
|
+
>;
|
|
152
|
+
type DocxChildrenCreateChild = NonNullable<
|
|
153
|
+
NonNullable<DocxChildrenCreatePayload["data"]>["children"]
|
|
154
|
+
>[number];
|
|
155
|
+
type DocxDescendantCreatePayload = NonNullable<
|
|
156
|
+
Parameters<Lark.Client["docx"]["documentBlockDescendant"]["create"]>[0]
|
|
157
|
+
>;
|
|
158
|
+
type DocxDescendantCreateBlock = NonNullable<
|
|
159
|
+
NonNullable<DocxDescendantCreatePayload["data"]>["descendants"]
|
|
160
|
+
>[number];
|
|
161
|
+
type DriveMediaUploadAllPayload = NonNullable<
|
|
162
|
+
Parameters<Lark.Client["drive"]["media"]["uploadAll"]>[0]
|
|
163
|
+
>;
|
|
164
|
+
type DriveMediaUploadFile = NonNullable<NonNullable<DriveMediaUploadAllPayload["data"]>["file"]>;
|
|
165
|
+
|
|
166
|
+
function toCreateChildBlock(block: FeishuDocxBlock): DocxChildrenCreateChild {
|
|
167
|
+
return block as DocxChildrenCreateChild;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function toDescendantBlock(block: FeishuDocxBlock): DocxDescendantCreateBlock {
|
|
171
|
+
const children = normalizeChildIds(block.children);
|
|
172
|
+
return {
|
|
173
|
+
...(block.block_id ? { block_id: block.block_id } : {}),
|
|
174
|
+
...(children.length > 0 ? { children } : {}),
|
|
175
|
+
...block,
|
|
176
|
+
} as DocxDescendantCreateBlock;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function normalizeInsertedChildBlocks(
|
|
180
|
+
children: string[] | FeishuDocxBlockChild[] | undefined,
|
|
181
|
+
): FeishuDocxBlockChild[] {
|
|
182
|
+
if (!Array.isArray(children)) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
return children.filter(
|
|
186
|
+
(child): child is FeishuDocxBlockChild => typeof child === "object" && child !== null,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Convert API may return `blocks` in a non-render order.
|
|
191
|
+
// Reconstruct the document tree using first_level_block_ids plus children/parent links,
|
|
192
|
+
// then emit blocks in pre-order so Descendant/Children APIs receive one normalized tree contract.
|
|
193
|
+
function normalizeConvertedBlockTree(
|
|
194
|
+
blocks: FeishuDocxBlock[],
|
|
195
|
+
firstLevelIds: string[],
|
|
196
|
+
): { orderedBlocks: FeishuDocxBlock[]; rootIds: string[] } {
|
|
197
|
+
if (blocks.length <= 1) {
|
|
198
|
+
const rootIds =
|
|
199
|
+
blocks.length === 1 && typeof blocks[0]?.block_id === "string" ? [blocks[0].block_id] : [];
|
|
200
|
+
return { orderedBlocks: blocks, rootIds };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const byId = new Map<string, FeishuDocxBlock>();
|
|
204
|
+
const originalOrder = new Map<string, number>();
|
|
205
|
+
for (const [index, block] of blocks.entries()) {
|
|
206
|
+
if (typeof block?.block_id === "string") {
|
|
207
|
+
byId.set(block.block_id, block);
|
|
208
|
+
originalOrder.set(block.block_id, index);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const childIds = new Set<string>();
|
|
213
|
+
for (const block of blocks) {
|
|
214
|
+
for (const childId of normalizeChildIds(block?.children)) {
|
|
215
|
+
childIds.add(childId);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const inferredTopLevelIds = blocks
|
|
220
|
+
.filter((block) => {
|
|
221
|
+
const blockId = block?.block_id;
|
|
222
|
+
if (typeof blockId !== "string") {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const parentId = typeof block?.parent_id === "string" ? block.parent_id : "";
|
|
226
|
+
return !childIds.has(blockId) && (!parentId || !byId.has(parentId));
|
|
227
|
+
})
|
|
228
|
+
.toSorted(
|
|
229
|
+
(a, b) =>
|
|
230
|
+
(originalOrder.get(a.block_id ?? "__missing__") ?? 0) -
|
|
231
|
+
(originalOrder.get(b.block_id ?? "__missing__") ?? 0),
|
|
232
|
+
)
|
|
233
|
+
.map((block) => block.block_id)
|
|
234
|
+
.filter((blockId): blockId is string => typeof blockId === "string");
|
|
235
|
+
|
|
236
|
+
const rootIds = (
|
|
237
|
+
firstLevelIds && firstLevelIds.length > 0 ? firstLevelIds : inferredTopLevelIds
|
|
238
|
+
).filter((id, index, arr) => typeof id === "string" && byId.has(id) && arr.indexOf(id) === index);
|
|
239
|
+
|
|
240
|
+
const orderedBlocks: FeishuDocxBlock[] = [];
|
|
241
|
+
const visited = new Set<string>();
|
|
242
|
+
|
|
243
|
+
const visit = (blockId: string) => {
|
|
244
|
+
if (!byId.has(blockId) || visited.has(blockId)) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
visited.add(blockId);
|
|
248
|
+
const block = byId.get(blockId);
|
|
249
|
+
if (!block) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
orderedBlocks.push(block);
|
|
253
|
+
for (const childId of normalizeChildIds(block?.children)) {
|
|
254
|
+
visit(childId);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
for (const rootId of rootIds) {
|
|
259
|
+
visit(rootId);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Fallback for malformed/partial trees from Convert API: keep any leftovers in original order.
|
|
263
|
+
for (const block of blocks) {
|
|
264
|
+
if (typeof block?.block_id === "string") {
|
|
265
|
+
visit(block.block_id);
|
|
266
|
+
} else {
|
|
267
|
+
orderedBlocks.push(block);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { orderedBlocks, rootIds: rootIds.filter((id): id is string => typeof id === "string") };
|
|
122
272
|
}
|
|
123
273
|
|
|
124
|
-
/* eslint-disable @typescript-eslint/no-explicit-any -- SDK block types */
|
|
125
274
|
async function insertBlocks(
|
|
126
275
|
client: Lark.Client,
|
|
127
276
|
docToken: string,
|
|
128
|
-
blocks:
|
|
277
|
+
blocks: FeishuDocxBlock[],
|
|
129
278
|
parentBlockId?: string,
|
|
130
279
|
index?: number,
|
|
131
|
-
): Promise<{ children:
|
|
132
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
280
|
+
): Promise<{ children: FeishuDocxBlockChild[]; skipped: string[] }> {
|
|
133
281
|
const { cleaned, skipped } = cleanBlocksForInsert(blocks);
|
|
134
282
|
const blockId = parentBlockId ?? docToken;
|
|
135
283
|
|
|
@@ -141,12 +289,12 @@ async function insertBlocks(
|
|
|
141
289
|
// The batch API (sending all children at once) does not guarantee ordering
|
|
142
290
|
// because Feishu processes the batch asynchronously. Sequential single-block
|
|
143
291
|
// inserts (each appended to the end) produce deterministic results.
|
|
144
|
-
const allInserted:
|
|
292
|
+
const allInserted: FeishuDocxBlockChild[] = [];
|
|
145
293
|
for (const [offset, block] of cleaned.entries()) {
|
|
146
294
|
const res = await client.docx.documentBlockChildren.create({
|
|
147
295
|
path: { document_id: docToken, block_id: blockId },
|
|
148
296
|
data: {
|
|
149
|
-
children: [block],
|
|
297
|
+
children: [toCreateChildBlock(block)],
|
|
150
298
|
...(index !== undefined ? { index: index + offset } : {}),
|
|
151
299
|
},
|
|
152
300
|
});
|
|
@@ -240,8 +388,7 @@ async function convertMarkdownWithFallback(client: Lark.Client, markdown: string
|
|
|
240
388
|
throw error;
|
|
241
389
|
}
|
|
242
390
|
|
|
243
|
-
|
|
244
|
-
const blocks: any[] = [];
|
|
391
|
+
const blocks: FeishuDocxBlock[] = [];
|
|
245
392
|
const firstLevelBlockIds: string[] = [];
|
|
246
393
|
|
|
247
394
|
for (const chunk of chunks) {
|
|
@@ -257,29 +404,25 @@ async function convertMarkdownWithFallback(client: Lark.Client, markdown: string
|
|
|
257
404
|
/** Convert markdown in chunks to avoid document.convert content size limits */
|
|
258
405
|
async function chunkedConvertMarkdown(client: Lark.Client, markdown: string) {
|
|
259
406
|
const chunks = splitMarkdownByHeadings(markdown);
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
const allFirstLevelBlockIds: string[] = [];
|
|
407
|
+
const allBlocks: FeishuDocxBlock[] = [];
|
|
408
|
+
const allRootIds: string[] = [];
|
|
263
409
|
for (const chunk of chunks) {
|
|
264
410
|
const { blocks, firstLevelBlockIds } = await convertMarkdownWithFallback(client, chunk);
|
|
265
|
-
const
|
|
266
|
-
allBlocks.push(...
|
|
267
|
-
|
|
411
|
+
const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
|
|
412
|
+
allBlocks.push(...orderedBlocks);
|
|
413
|
+
allRootIds.push(...rootIds);
|
|
268
414
|
}
|
|
269
|
-
return { blocks: allBlocks, firstLevelBlockIds:
|
|
415
|
+
return { blocks: allBlocks, firstLevelBlockIds: allRootIds };
|
|
270
416
|
}
|
|
271
417
|
|
|
272
418
|
/** Insert blocks in batches of MAX_BLOCKS_PER_INSERT to avoid API 400 errors */
|
|
273
|
-
|
|
274
|
-
async function chunkedInsertBlocks(
|
|
419
|
+
async function _chunkedInsertBlocks(
|
|
275
420
|
client: Lark.Client,
|
|
276
421
|
docToken: string,
|
|
277
|
-
blocks:
|
|
422
|
+
blocks: FeishuDocxBlock[],
|
|
278
423
|
parentBlockId?: string,
|
|
279
|
-
): Promise<{ children:
|
|
280
|
-
|
|
281
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block types
|
|
282
|
-
const allChildren: any[] = [];
|
|
424
|
+
): Promise<{ children: FeishuDocxBlockChild[]; skipped: string[] }> {
|
|
425
|
+
const allChildren: FeishuDocxBlockChild[] = [];
|
|
283
426
|
const allSkipped: string[] = [];
|
|
284
427
|
|
|
285
428
|
for (let i = 0; i < blocks.length; i += MAX_BLOCKS_PER_INSERT) {
|
|
@@ -301,15 +444,13 @@ type Logger = { info?: (msg: string) => void };
|
|
|
301
444
|
* @param parentBlockId - Parent block to insert into (defaults to docToken = document root)
|
|
302
445
|
* @param index - Position within parent's children (-1 = end, 0 = first)
|
|
303
446
|
*/
|
|
304
|
-
/* eslint-disable @typescript-eslint/no-explicit-any -- SDK block types */
|
|
305
447
|
async function insertBlocksWithDescendant(
|
|
306
448
|
client: Lark.Client,
|
|
307
449
|
docToken: string,
|
|
308
|
-
blocks:
|
|
450
|
+
blocks: FeishuDocxBlock[],
|
|
309
451
|
firstLevelBlockIds: string[],
|
|
310
452
|
{ parentBlockId = docToken, index = -1 }: { parentBlockId?: string; index?: number } = {},
|
|
311
|
-
): Promise<{ children:
|
|
312
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
453
|
+
): Promise<{ children: FeishuDocxBlockChild[] }> {
|
|
313
454
|
const descendants = cleanBlocksForDescendant(blocks);
|
|
314
455
|
if (descendants.length === 0) {
|
|
315
456
|
return { children: [] };
|
|
@@ -317,7 +458,11 @@ async function insertBlocksWithDescendant(
|
|
|
317
458
|
|
|
318
459
|
const res = await client.docx.documentBlockDescendant.create({
|
|
319
460
|
path: { document_id: docToken, block_id: parentBlockId },
|
|
320
|
-
data: {
|
|
461
|
+
data: {
|
|
462
|
+
children_id: firstLevelBlockIds,
|
|
463
|
+
descendants: descendants.map(toDescendantBlock),
|
|
464
|
+
index,
|
|
465
|
+
},
|
|
321
466
|
});
|
|
322
467
|
|
|
323
468
|
if (res.code !== 0) {
|
|
@@ -369,8 +514,7 @@ async function uploadImageToDocx(
|
|
|
369
514
|
// Pass Buffer directly so form-data can calculate Content-Length correctly.
|
|
370
515
|
// Readable.from() produces a stream with unknown length, causing Content-Length
|
|
371
516
|
// mismatch that silently truncates uploads for images larger than ~1KB.
|
|
372
|
-
|
|
373
|
-
file: imageBuffer as any,
|
|
517
|
+
file: imageBuffer as DriveMediaUploadFile,
|
|
374
518
|
// Required when the document block belongs to a non-default datacenter:
|
|
375
519
|
// tells the drive service which document the block belongs to for routing.
|
|
376
520
|
// Per API docs: certain upload scenarios require the cloud document token.
|
|
@@ -394,6 +538,7 @@ async function resolveUploadInput(
|
|
|
394
538
|
url: string | undefined,
|
|
395
539
|
filePath: string | undefined,
|
|
396
540
|
maxBytes: number,
|
|
541
|
+
localRoots?: readonly string[],
|
|
397
542
|
explicitFileName?: string,
|
|
398
543
|
imageInput?: string, // data URI, plain base64, or local path
|
|
399
544
|
): Promise<{ buffer: Buffer; fileName: string }> {
|
|
@@ -458,11 +603,14 @@ async function resolveUploadInput(
|
|
|
458
603
|
const absolutePath = isAbsolute(imageInput);
|
|
459
604
|
|
|
460
605
|
if (unambiguousPath || (absolutePath && existsSync(candidate))) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
606
|
+
// Use loadWebMedia to enforce localRoots sandbox (same as sendMediaFeishu).
|
|
607
|
+
const resolvedPath = resolve(candidate);
|
|
608
|
+
const loaded = await getFeishuRuntime().media.loadWebMedia(resolvedPath, {
|
|
609
|
+
maxBytes,
|
|
610
|
+
optimizeImages: false,
|
|
611
|
+
localRoots,
|
|
612
|
+
});
|
|
613
|
+
return { buffer: loaded.buffer, fileName: explicitFileName ?? basename(candidate) };
|
|
466
614
|
}
|
|
467
615
|
|
|
468
616
|
if (absolutePath && !existsSync(candidate)) {
|
|
@@ -516,25 +664,26 @@ async function resolveUploadInput(
|
|
|
516
664
|
};
|
|
517
665
|
}
|
|
518
666
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
667
|
+
// Use loadWebMedia to enforce localRoots sandbox (same as sendMediaFeishu).
|
|
668
|
+
const resolvedFilePath = resolve(filePath!);
|
|
669
|
+
const loaded = await getFeishuRuntime().media.loadWebMedia(resolvedFilePath, {
|
|
670
|
+
maxBytes,
|
|
671
|
+
optimizeImages: false,
|
|
672
|
+
localRoots,
|
|
673
|
+
});
|
|
523
674
|
return {
|
|
524
|
-
buffer,
|
|
675
|
+
buffer: loaded.buffer,
|
|
525
676
|
fileName: explicitFileName || basename(filePath!),
|
|
526
677
|
};
|
|
527
678
|
}
|
|
528
679
|
|
|
529
|
-
/* eslint-disable @typescript-eslint/no-explicit-any -- SDK block types */
|
|
530
680
|
async function processImages(
|
|
531
681
|
client: Lark.Client,
|
|
532
682
|
docToken: string,
|
|
533
683
|
markdown: string,
|
|
534
|
-
insertedBlocks:
|
|
684
|
+
insertedBlocks: FeishuDocxBlockChild[],
|
|
535
685
|
maxBytes: number,
|
|
536
686
|
): Promise<number> {
|
|
537
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
538
687
|
const imageUrls = extractImageUrls(markdown);
|
|
539
688
|
if (imageUrls.length === 0) {
|
|
540
689
|
return 0;
|
|
@@ -545,7 +694,10 @@ async function processImages(
|
|
|
545
694
|
let processed = 0;
|
|
546
695
|
for (let i = 0; i < Math.min(imageUrls.length, imageBlocks.length); i++) {
|
|
547
696
|
const url = imageUrls[i];
|
|
548
|
-
const blockId = imageBlocks[i]
|
|
697
|
+
const blockId = imageBlocks[i]?.block_id;
|
|
698
|
+
if (!blockId) {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
549
701
|
|
|
550
702
|
try {
|
|
551
703
|
const buffer = await downloadImage(url, maxBytes);
|
|
@@ -573,6 +725,7 @@ async function uploadImageBlock(
|
|
|
573
725
|
client: Lark.Client,
|
|
574
726
|
docToken: string,
|
|
575
727
|
maxBytes: number,
|
|
728
|
+
localRoots?: readonly string[],
|
|
576
729
|
url?: string,
|
|
577
730
|
filePath?: string,
|
|
578
731
|
parentBlockId?: string,
|
|
@@ -585,20 +738,25 @@ async function uploadImageBlock(
|
|
|
585
738
|
const insertRes = await client.docx.documentBlockChildren.create({
|
|
586
739
|
path: { document_id: docToken, block_id: parentBlockId ?? docToken },
|
|
587
740
|
params: { document_revision_id: -1 },
|
|
588
|
-
|
|
589
|
-
data: { children: [{ block_type: 27, image: {} as any }], index: index ?? -1 },
|
|
741
|
+
data: { children: [{ block_type: 27, image: {} }], index: index ?? -1 },
|
|
590
742
|
});
|
|
591
743
|
if (insertRes.code !== 0) {
|
|
592
744
|
throw new Error(`Failed to create image block: ${insertRes.msg}`);
|
|
593
745
|
}
|
|
594
|
-
|
|
595
|
-
const imageBlockId = insertRes.data?.children?.find((b: any) => b.block_type === 27)?.block_id;
|
|
746
|
+
const imageBlockId = insertRes.data?.children?.find((b) => b.block_type === 27)?.block_id;
|
|
596
747
|
if (!imageBlockId) {
|
|
597
748
|
throw new Error("Failed to create image block");
|
|
598
749
|
}
|
|
599
750
|
|
|
600
751
|
// Step 2: Resolve and upload the image buffer.
|
|
601
|
-
const upload = await resolveUploadInput(
|
|
752
|
+
const upload = await resolveUploadInput(
|
|
753
|
+
url,
|
|
754
|
+
filePath,
|
|
755
|
+
maxBytes,
|
|
756
|
+
localRoots,
|
|
757
|
+
filename,
|
|
758
|
+
imageInput,
|
|
759
|
+
);
|
|
602
760
|
const fileToken = await uploadImageToDocx(
|
|
603
761
|
client,
|
|
604
762
|
imageBlockId,
|
|
@@ -629,6 +787,7 @@ async function uploadFileBlock(
|
|
|
629
787
|
client: Lark.Client,
|
|
630
788
|
docToken: string,
|
|
631
789
|
maxBytes: number,
|
|
790
|
+
localRoots?: readonly string[],
|
|
632
791
|
url?: string,
|
|
633
792
|
filePath?: string,
|
|
634
793
|
parentBlockId?: string,
|
|
@@ -639,16 +798,18 @@ async function uploadFileBlock(
|
|
|
639
798
|
// Feishu API does not allow creating empty file blocks (block_type 23).
|
|
640
799
|
// Workaround: create a placeholder text block, then replace it with file content.
|
|
641
800
|
// Actually, file blocks need a different approach: use markdown link as placeholder.
|
|
642
|
-
const upload = await resolveUploadInput(url, filePath, maxBytes, filename);
|
|
801
|
+
const upload = await resolveUploadInput(url, filePath, maxBytes, localRoots, filename);
|
|
643
802
|
|
|
644
803
|
// Create a placeholder text block first
|
|
645
804
|
const placeholderMd = `[${upload.fileName}](https://example.com/placeholder)`;
|
|
646
805
|
const converted = await convertMarkdown(client, placeholderMd);
|
|
647
|
-
const
|
|
648
|
-
|
|
806
|
+
const { orderedBlocks } = normalizeConvertedBlockTree(
|
|
807
|
+
converted.blocks,
|
|
808
|
+
converted.firstLevelBlockIds,
|
|
809
|
+
);
|
|
810
|
+
const { children: inserted } = await insertBlocks(client, docToken, orderedBlocks, blockId);
|
|
649
811
|
|
|
650
812
|
// Get the first inserted block - we'll delete it and create the file in its place
|
|
651
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return shape
|
|
652
813
|
const placeholderBlock = inserted[0];
|
|
653
814
|
if (!placeholderBlock?.block_id) {
|
|
654
815
|
throw new Error("Failed to create placeholder block for file upload");
|
|
@@ -663,10 +824,7 @@ async function uploadFileBlock(
|
|
|
663
824
|
throw new Error(childrenRes.msg);
|
|
664
825
|
}
|
|
665
826
|
const items = childrenRes.data?.items ?? [];
|
|
666
|
-
|
|
667
|
-
const placeholderIdx = items.findIndex(
|
|
668
|
-
(item: any) => item.block_id === placeholderBlock.block_id,
|
|
669
|
-
);
|
|
827
|
+
const placeholderIdx = items.findIndex((item) => item.block_id === placeholderBlock.block_id);
|
|
670
828
|
if (placeholderIdx >= 0) {
|
|
671
829
|
const deleteRes = await client.docx.documentBlockChildren.batchDelete({
|
|
672
830
|
path: { document_id: docToken, block_id: parentId },
|
|
@@ -684,8 +842,7 @@ async function uploadFileBlock(
|
|
|
684
842
|
parent_type: "docx_file",
|
|
685
843
|
parent_node: docToken,
|
|
686
844
|
size: upload.buffer.length,
|
|
687
|
-
|
|
688
|
-
file: upload.buffer as any,
|
|
845
|
+
file: upload.buffer as DriveMediaUploadFile,
|
|
689
846
|
},
|
|
690
847
|
});
|
|
691
848
|
|
|
@@ -766,7 +923,7 @@ async function createDoc(
|
|
|
766
923
|
}
|
|
767
924
|
const shouldGrantToRequester = options?.grantToRequester !== false;
|
|
768
925
|
const requesterOpenId = options?.requesterOpenId?.trim();
|
|
769
|
-
const requesterPermType
|
|
926
|
+
const requesterPermType = "edit" as const;
|
|
770
927
|
|
|
771
928
|
let requesterPermissionAdded = false;
|
|
772
929
|
let requesterPermissionSkippedReason: string | undefined;
|
|
@@ -788,7 +945,7 @@ async function createDoc(
|
|
|
788
945
|
});
|
|
789
946
|
requesterPermissionAdded = true;
|
|
790
947
|
} catch (err) {
|
|
791
|
-
requesterPermissionError =
|
|
948
|
+
requesterPermissionError = formatErrorMessage(err);
|
|
792
949
|
}
|
|
793
950
|
}
|
|
794
951
|
}
|
|
@@ -824,11 +981,11 @@ async function writeDoc(
|
|
|
824
981
|
}
|
|
825
982
|
|
|
826
983
|
logger?.info?.(`feishu_doc: Converted to ${blocks.length} blocks, inserting...`);
|
|
827
|
-
const
|
|
984
|
+
const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
|
|
828
985
|
const { children: inserted } =
|
|
829
986
|
blocks.length > BATCH_SIZE
|
|
830
|
-
? await insertBlocksInBatches(client, docToken,
|
|
831
|
-
: await insertBlocksWithDescendant(client, docToken,
|
|
987
|
+
? await insertBlocksInBatches(client, docToken, orderedBlocks, rootIds, logger)
|
|
988
|
+
: await insertBlocksWithDescendant(client, docToken, orderedBlocks, rootIds);
|
|
832
989
|
const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
|
|
833
990
|
logger?.info?.(`feishu_doc: Done (${blocks.length} blocks, ${imagesProcessed} images)`);
|
|
834
991
|
|
|
@@ -854,11 +1011,11 @@ async function appendDoc(
|
|
|
854
1011
|
}
|
|
855
1012
|
|
|
856
1013
|
logger?.info?.(`feishu_doc: Converted to ${blocks.length} blocks, inserting...`);
|
|
857
|
-
const
|
|
1014
|
+
const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
|
|
858
1015
|
const { children: inserted } =
|
|
859
1016
|
blocks.length > BATCH_SIZE
|
|
860
|
-
? await insertBlocksInBatches(client, docToken,
|
|
861
|
-
: await insertBlocksWithDescendant(client, docToken,
|
|
1017
|
+
? await insertBlocksInBatches(client, docToken, orderedBlocks, rootIds, logger)
|
|
1018
|
+
: await insertBlocksWithDescendant(client, docToken, orderedBlocks, rootIds);
|
|
862
1019
|
const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes);
|
|
863
1020
|
logger?.info?.(`feishu_doc: Done (${blocks.length} blocks, ${imagesProcessed} images)`);
|
|
864
1021
|
|
|
@@ -866,8 +1023,7 @@ async function appendDoc(
|
|
|
866
1023
|
success: true,
|
|
867
1024
|
blocks_added: blocks.length,
|
|
868
1025
|
images_processed: imagesProcessed,
|
|
869
|
-
|
|
870
|
-
block_ids: inserted.map((b: any) => b.block_id),
|
|
1026
|
+
block_ids: inserted.map((b) => b.block_id),
|
|
871
1027
|
};
|
|
872
1028
|
}
|
|
873
1029
|
|
|
@@ -882,22 +1038,25 @@ async function insertDoc(
|
|
|
882
1038
|
const blockInfo = await client.docx.documentBlock.get({
|
|
883
1039
|
path: { document_id: docToken, block_id: afterBlockId },
|
|
884
1040
|
});
|
|
885
|
-
if (blockInfo.code !== 0)
|
|
1041
|
+
if (blockInfo.code !== 0) {
|
|
1042
|
+
throw new Error(blockInfo.msg);
|
|
1043
|
+
}
|
|
886
1044
|
|
|
887
1045
|
const parentId = blockInfo.data?.block?.parent_id ?? docToken;
|
|
888
1046
|
|
|
889
1047
|
// Paginate through all children to reliably locate after_block_id.
|
|
890
1048
|
// documentBlockChildren.get returns up to 200 children per page; large
|
|
891
1049
|
// parents require multiple requests.
|
|
892
|
-
|
|
893
|
-
const items: any[] = [];
|
|
1050
|
+
const items: FeishuDocxBlock[] = [];
|
|
894
1051
|
let pageToken: string | undefined;
|
|
895
1052
|
do {
|
|
896
1053
|
const childrenRes = await client.docx.documentBlockChildren.get({
|
|
897
1054
|
path: { document_id: docToken, block_id: parentId },
|
|
898
1055
|
params: pageToken ? { page_token: pageToken } : {},
|
|
899
1056
|
});
|
|
900
|
-
if (childrenRes.code !== 0)
|
|
1057
|
+
if (childrenRes.code !== 0) {
|
|
1058
|
+
throw new Error(childrenRes.msg);
|
|
1059
|
+
}
|
|
901
1060
|
items.push(...(childrenRes.data?.items ?? []));
|
|
902
1061
|
pageToken = childrenRes.data?.page_token ?? undefined;
|
|
903
1062
|
} while (pageToken);
|
|
@@ -913,8 +1072,10 @@ async function insertDoc(
|
|
|
913
1072
|
|
|
914
1073
|
logger?.info?.("feishu_doc: Converting markdown...");
|
|
915
1074
|
const { blocks, firstLevelBlockIds } = await chunkedConvertMarkdown(client, markdown);
|
|
916
|
-
if (blocks.length === 0)
|
|
917
|
-
|
|
1075
|
+
if (blocks.length === 0) {
|
|
1076
|
+
throw new Error("Content is empty");
|
|
1077
|
+
}
|
|
1078
|
+
const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
|
|
918
1079
|
|
|
919
1080
|
logger?.info?.(
|
|
920
1081
|
`feishu_doc: Converted to ${blocks.length} blocks, inserting at index ${insertIndex}...`,
|
|
@@ -924,13 +1085,13 @@ async function insertDoc(
|
|
|
924
1085
|
? await insertBlocksInBatches(
|
|
925
1086
|
client,
|
|
926
1087
|
docToken,
|
|
927
|
-
|
|
928
|
-
|
|
1088
|
+
orderedBlocks,
|
|
1089
|
+
rootIds,
|
|
929
1090
|
logger,
|
|
930
1091
|
parentId,
|
|
931
1092
|
insertIndex,
|
|
932
1093
|
)
|
|
933
|
-
: await insertBlocksWithDescendant(client, docToken,
|
|
1094
|
+
: await insertBlocksWithDescendant(client, docToken, orderedBlocks, rootIds, {
|
|
934
1095
|
parentBlockId: parentId,
|
|
935
1096
|
index: insertIndex,
|
|
936
1097
|
});
|
|
@@ -942,8 +1103,7 @@ async function insertDoc(
|
|
|
942
1103
|
success: true,
|
|
943
1104
|
blocks_added: blocks.length,
|
|
944
1105
|
images_processed: imagesProcessed,
|
|
945
|
-
|
|
946
|
-
block_ids: inserted.map((b: any) => b.block_id),
|
|
1106
|
+
block_ids: inserted.map((b) => b.block_id),
|
|
947
1107
|
};
|
|
948
1108
|
}
|
|
949
1109
|
|
|
@@ -982,10 +1142,8 @@ async function createTable(
|
|
|
982
1142
|
throw new Error(res.msg);
|
|
983
1143
|
}
|
|
984
1144
|
|
|
985
|
-
|
|
986
|
-
const
|
|
987
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK return shape may vary by version
|
|
988
|
-
const cells = (tableBlock?.children as any[] | undefined) ?? [];
|
|
1145
|
+
const tableBlock = res.data?.children?.find((b) => b.block_type === 31);
|
|
1146
|
+
const cells = normalizeInsertedChildBlocks(tableBlock?.children);
|
|
989
1147
|
|
|
990
1148
|
return {
|
|
991
1149
|
success: true,
|
|
@@ -993,8 +1151,7 @@ async function createTable(
|
|
|
993
1151
|
row_size: rowSize,
|
|
994
1152
|
column_size: columnSize,
|
|
995
1153
|
// row-major cell ids, if API returns them directly
|
|
996
|
-
|
|
997
|
-
table_cell_block_ids: cells.map((c: any) => c.block_id).filter(Boolean),
|
|
1154
|
+
table_cell_block_ids: cells.map((c) => c.block_id).filter(Boolean),
|
|
998
1155
|
raw_children_count: res.data?.children?.length ?? 0,
|
|
999
1156
|
};
|
|
1000
1157
|
}
|
|
@@ -1021,13 +1178,10 @@ async function writeTableCells(
|
|
|
1021
1178
|
throw new Error("table_block_id is not a table block");
|
|
1022
1179
|
}
|
|
1023
1180
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const
|
|
1027
|
-
const
|
|
1028
|
-
const cols = tableData?.property?.column_size as number | undefined;
|
|
1029
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block payload
|
|
1030
|
-
const cellIds = (tableData?.cells as any[] | undefined) ?? [];
|
|
1181
|
+
const tableData = tableBlock.table;
|
|
1182
|
+
const rows = tableData?.property?.row_size;
|
|
1183
|
+
const cols = tableData?.property?.column_size;
|
|
1184
|
+
const cellIds = tableData?.cells ?? [];
|
|
1031
1185
|
|
|
1032
1186
|
if (!rows || !cols || !cellIds.length) {
|
|
1033
1187
|
throw new Error(
|
|
@@ -1044,7 +1198,9 @@ async function writeTableCells(
|
|
|
1044
1198
|
|
|
1045
1199
|
for (let c = 0; c < writeCols; c++) {
|
|
1046
1200
|
const cellId = cellIds[r * cols + c];
|
|
1047
|
-
if (!cellId)
|
|
1201
|
+
if (!cellId) {
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1048
1204
|
|
|
1049
1205
|
// table cell is a container block: clear existing children, then create text child blocks
|
|
1050
1206
|
const childrenRes = await client.docx.documentBlockChildren.get({
|
|
@@ -1067,10 +1223,13 @@ async function writeTableCells(
|
|
|
1067
1223
|
|
|
1068
1224
|
const text = rowValues[c] ?? "";
|
|
1069
1225
|
const converted = await convertMarkdown(client, text);
|
|
1070
|
-
const
|
|
1226
|
+
const { orderedBlocks } = normalizeConvertedBlockTree(
|
|
1227
|
+
converted.blocks,
|
|
1228
|
+
converted.firstLevelBlockIds,
|
|
1229
|
+
);
|
|
1071
1230
|
|
|
1072
|
-
if (
|
|
1073
|
-
await insertBlocks(client, docToken,
|
|
1231
|
+
if (orderedBlocks.length > 0) {
|
|
1232
|
+
await insertBlocks(client, docToken, orderedBlocks, cellId);
|
|
1074
1233
|
}
|
|
1075
1234
|
|
|
1076
1235
|
written++;
|
|
@@ -1164,8 +1323,7 @@ async function deleteBlock(client: Lark.Client, docToken: string, blockId: strin
|
|
|
1164
1323
|
}
|
|
1165
1324
|
|
|
1166
1325
|
const items = children.data?.items ?? [];
|
|
1167
|
-
|
|
1168
|
-
const index = items.findIndex((item: any) => item.block_id === blockId);
|
|
1326
|
+
const index = items.findIndex((item) => item.block_id === blockId);
|
|
1169
1327
|
if (index === -1) {
|
|
1170
1328
|
throw new Error("Block not found");
|
|
1171
1329
|
}
|
|
@@ -1228,14 +1386,12 @@ async function listAppScopes(client: Lark.Client) {
|
|
|
1228
1386
|
|
|
1229
1387
|
export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
1230
1388
|
if (!api.config) {
|
|
1231
|
-
api.logger.debug?.("feishu_doc: No config available, skipping doc tools");
|
|
1232
1389
|
return;
|
|
1233
1390
|
}
|
|
1234
1391
|
|
|
1235
1392
|
// Check if any account is configured
|
|
1236
1393
|
const accounts = listEnabledFeishuAccounts(api.config);
|
|
1237
1394
|
if (accounts.length === 0) {
|
|
1238
|
-
api.logger.debug?.("feishu_doc: No Feishu accounts configured, skipping doc tools");
|
|
1239
1395
|
return;
|
|
1240
1396
|
}
|
|
1241
1397
|
|
|
@@ -1262,8 +1418,11 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
1262
1418
|
api.registerTool(
|
|
1263
1419
|
(ctx) => {
|
|
1264
1420
|
const defaultAccountId = ctx.agentAccountId;
|
|
1421
|
+
const mediaLocalRoots = resolveDocToolLocalRoots(ctx);
|
|
1265
1422
|
const trustedRequesterOpenId =
|
|
1266
|
-
ctx.messageChannel === "feishu"
|
|
1423
|
+
ctx.messageChannel === "feishu"
|
|
1424
|
+
? normalizeOptionalString(ctx.requesterSenderId)
|
|
1425
|
+
: undefined;
|
|
1267
1426
|
return {
|
|
1268
1427
|
name: "feishu_doc",
|
|
1269
1428
|
label: "Feishu Doc",
|
|
@@ -1356,6 +1515,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
1356
1515
|
client,
|
|
1357
1516
|
p.doc_token,
|
|
1358
1517
|
getMediaMaxBytes(p, defaultAccountId),
|
|
1518
|
+
mediaLocalRoots,
|
|
1359
1519
|
p.url,
|
|
1360
1520
|
p.file_path,
|
|
1361
1521
|
p.parent_block_id,
|
|
@@ -1370,6 +1530,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
1370
1530
|
client,
|
|
1371
1531
|
p.doc_token,
|
|
1372
1532
|
getMediaMaxBytes(p, defaultAccountId),
|
|
1533
|
+
mediaLocalRoots,
|
|
1373
1534
|
p.url,
|
|
1374
1535
|
p.file_path,
|
|
1375
1536
|
p.parent_block_id,
|
|
@@ -1417,11 +1578,10 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
1417
1578
|
),
|
|
1418
1579
|
);
|
|
1419
1580
|
default:
|
|
1420
|
-
|
|
1421
|
-
return json({ error: `Unknown action: ${(p as any).action}` });
|
|
1581
|
+
return json({ error: "Unknown action" });
|
|
1422
1582
|
}
|
|
1423
1583
|
} catch (err) {
|
|
1424
|
-
return json({ error:
|
|
1584
|
+
return json({ error: formatErrorMessage(err) });
|
|
1425
1585
|
}
|
|
1426
1586
|
},
|
|
1427
1587
|
};
|
|
@@ -1445,7 +1605,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
1445
1605
|
const result = await listAppScopes(getClient(undefined, ctx.agentAccountId));
|
|
1446
1606
|
return json(result);
|
|
1447
1607
|
} catch (err) {
|
|
1448
|
-
return json({ error:
|
|
1608
|
+
return json({ error: formatErrorMessage(err) });
|
|
1449
1609
|
}
|
|
1450
1610
|
},
|
|
1451
1611
|
}),
|
|
@@ -1453,8 +1613,4 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
1453
1613
|
);
|
|
1454
1614
|
registered.push("feishu_app_scopes");
|
|
1455
1615
|
}
|
|
1456
|
-
|
|
1457
|
-
if (registered.length > 0) {
|
|
1458
|
-
api.logger.info?.(`feishu_doc: Registered ${registered.join(", ")}`);
|
|
1459
|
-
}
|
|
1460
1616
|
}
|