Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
223
extensions/feishu/src/docx-batch-insert.ts
Normal file
223
extensions/feishu/src/docx-batch-insert.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Batch insertion for large Feishu documents (>1000 blocks).
|
||||
*
|
||||
* The Feishu Descendant API has a limit of 1000 blocks per request.
|
||||
* This module handles splitting large documents into batches while
|
||||
* preserving parent-child relationships between blocks.
|
||||
*/
|
||||
|
||||
import type * as Lark from "@larksuiteoapi/node-sdk";
|
||||
import { readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { cleanBlocksForDescendant } from "./docx-table-ops.js";
|
||||
import type { FeishuDocxBlock, FeishuDocxBlockChild } from "./docx-types.js";
|
||||
|
||||
export const BATCH_SIZE = 1000; // Feishu API limit per request
|
||||
|
||||
type Logger = { info?: (msg: string) => void };
|
||||
|
||||
type DocxDescendantCreatePayload = NonNullable<
|
||||
Parameters<Lark.Client["docx"]["documentBlockDescendant"]["create"]>[0]
|
||||
>;
|
||||
type DocxDescendantCreateBlock = NonNullable<
|
||||
NonNullable<DocxDescendantCreatePayload["data"]>["descendants"]
|
||||
>[number];
|
||||
|
||||
function normalizeChildIds(children: string[] | string | undefined): string[] | undefined {
|
||||
if (Array.isArray(children)) {
|
||||
return children;
|
||||
}
|
||||
const child = readStringValue(children);
|
||||
return child ? [child] : undefined;
|
||||
}
|
||||
|
||||
function toDescendantBlock(block: FeishuDocxBlock): DocxDescendantCreateBlock {
|
||||
const children = normalizeChildIds(block.children);
|
||||
return {
|
||||
...block,
|
||||
...(children ? { children } : {}),
|
||||
} as DocxDescendantCreateBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all descendant blocks for a given first-level block ID.
|
||||
* Recursively traverses the block tree to gather all children.
|
||||
*/
|
||||
function collectDescendants(
|
||||
blockMap: Map<string, FeishuDocxBlock>,
|
||||
rootId: string,
|
||||
): FeishuDocxBlock[] {
|
||||
const result: FeishuDocxBlock[] = [];
|
||||
const visited = new Set<string>();
|
||||
|
||||
function collect(blockId: string) {
|
||||
if (visited.has(blockId)) {
|
||||
return;
|
||||
}
|
||||
visited.add(blockId);
|
||||
|
||||
const block = blockMap.get(blockId);
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.push(block);
|
||||
|
||||
// Recursively collect children
|
||||
const children = block.children;
|
||||
if (Array.isArray(children)) {
|
||||
for (const childId of children) {
|
||||
collect(childId);
|
||||
}
|
||||
} else if (typeof children === "string") {
|
||||
collect(children);
|
||||
}
|
||||
}
|
||||
|
||||
collect(rootId);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a single batch of blocks using Descendant API.
|
||||
*
|
||||
* @param parentBlockId - Parent block to insert into (defaults to docToken)
|
||||
* @param index - Position within parent's children (-1 = end)
|
||||
*/
|
||||
async function insertBatch(
|
||||
client: Lark.Client,
|
||||
docToken: string,
|
||||
blocks: FeishuDocxBlock[],
|
||||
firstLevelBlockIds: string[],
|
||||
parentBlockId: string = docToken,
|
||||
index = -1,
|
||||
): Promise<FeishuDocxBlockChild[]> {
|
||||
const descendants = cleanBlocksForDescendant(blocks);
|
||||
|
||||
if (descendants.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const res = await client.docx.documentBlockDescendant.create({
|
||||
path: { document_id: docToken, block_id: parentBlockId },
|
||||
data: {
|
||||
children_id: firstLevelBlockIds,
|
||||
descendants: descendants.map(toDescendantBlock),
|
||||
index,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.code !== 0) {
|
||||
throw new Error(`${res.msg} (code: ${res.code})`);
|
||||
}
|
||||
|
||||
return res.data?.children ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert blocks in batches for large documents (>1000 blocks).
|
||||
*
|
||||
* Batches are split to ensure BOTH children_id AND descendants
|
||||
* arrays stay under the 1000 block API limit.
|
||||
*
|
||||
* @param client - Feishu API client
|
||||
* @param docToken - Document ID
|
||||
* @param blocks - All blocks from Convert API
|
||||
* @param firstLevelBlockIds - IDs of top-level blocks to insert
|
||||
* @param logger - Optional logger for progress updates
|
||||
* @param parentBlockId - Parent block to insert into (defaults to docToken = document root)
|
||||
* @param startIndex - Starting position within parent (-1 = end). For multi-batch inserts,
|
||||
* each batch advances this by the number of first-level IDs inserted so far.
|
||||
* @returns Inserted children blocks and any skipped block IDs
|
||||
*/
|
||||
export async function insertBlocksInBatches(
|
||||
client: Lark.Client,
|
||||
docToken: string,
|
||||
blocks: FeishuDocxBlock[],
|
||||
firstLevelBlockIds: string[],
|
||||
logger?: Logger,
|
||||
parentBlockId: string = docToken,
|
||||
startIndex = -1,
|
||||
): Promise<{ children: FeishuDocxBlockChild[]; skipped: string[] }> {
|
||||
const allChildren: FeishuDocxBlockChild[] = [];
|
||||
|
||||
// Build batches ensuring each batch has ≤1000 total descendants
|
||||
const batches: Array<{ firstLevelIds: string[]; blocks: FeishuDocxBlock[] }> = [];
|
||||
let currentBatch: { firstLevelIds: string[]; blocks: FeishuDocxBlock[] } = {
|
||||
firstLevelIds: [],
|
||||
blocks: [],
|
||||
};
|
||||
const usedBlockIds = new Set<string>();
|
||||
const blockMap = new Map<string, FeishuDocxBlock>();
|
||||
for (const block of blocks) {
|
||||
if (block.block_id) {
|
||||
blockMap.set(block.block_id, block);
|
||||
}
|
||||
}
|
||||
|
||||
for (const firstLevelId of firstLevelBlockIds) {
|
||||
const descendants = collectDescendants(blockMap, firstLevelId);
|
||||
const newBlocks = descendants.filter((b) => b.block_id && !usedBlockIds.has(b.block_id));
|
||||
|
||||
// A single block whose subtree exceeds the API limit cannot be split
|
||||
// (a table or other compound block must be inserted atomically).
|
||||
if (newBlocks.length > BATCH_SIZE) {
|
||||
throw new Error(
|
||||
`Block "${firstLevelId}" has ${newBlocks.length} descendants, which exceeds the ` +
|
||||
`Feishu API limit of ${BATCH_SIZE} blocks per request. ` +
|
||||
`Please split the content into smaller sections.`,
|
||||
);
|
||||
}
|
||||
|
||||
// If adding this first-level block would exceed limit, start new batch
|
||||
if (
|
||||
currentBatch.blocks.length + newBlocks.length > BATCH_SIZE &&
|
||||
currentBatch.blocks.length > 0
|
||||
) {
|
||||
batches.push(currentBatch);
|
||||
currentBatch = { firstLevelIds: [], blocks: [] };
|
||||
}
|
||||
|
||||
// Add to current batch
|
||||
currentBatch.firstLevelIds.push(firstLevelId);
|
||||
for (const block of newBlocks) {
|
||||
currentBatch.blocks.push(block);
|
||||
if (block.block_id) {
|
||||
usedBlockIds.add(block.block_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't forget the last batch
|
||||
if (currentBatch.blocks.length > 0) {
|
||||
batches.push(currentBatch);
|
||||
}
|
||||
|
||||
// Insert each batch, advancing index for position-aware inserts.
|
||||
// When startIndex == -1 (append to end), each batch appends after the previous.
|
||||
// When startIndex >= 0, each batch starts at startIndex + count of first-level IDs already inserted.
|
||||
let currentIndex = startIndex;
|
||||
for (let i = 0; i < batches.length; i++) {
|
||||
const batch = batches[i];
|
||||
logger?.info?.(
|
||||
`feishu_doc: Inserting batch ${i + 1}/${batches.length} (${batch.blocks.length} blocks)...`,
|
||||
);
|
||||
|
||||
const children = await insertBatch(
|
||||
client,
|
||||
docToken,
|
||||
batch.blocks,
|
||||
batch.firstLevelIds,
|
||||
parentBlockId,
|
||||
currentIndex,
|
||||
);
|
||||
allChildren.push(...children);
|
||||
|
||||
// Advance index only for explicit positions; -1 always means "after last inserted"
|
||||
if (currentIndex !== -1) {
|
||||
currentIndex += batch.firstLevelIds.length;
|
||||
}
|
||||
}
|
||||
|
||||
return { children: allChildren, skipped: [] };
|
||||
}
|
||||
Reference in New Issue
Block a user