feat(feishu): add markdown tables, positional insert, color_text, and table ops#29411
Conversation
|
Hi @Takhoffman, our previous PR #26790 was closed because part of it overlapped with #20304. This PR contains only the features that weren't covered there.
All features verified against the Feishu API. Would appreciate your review when you have a moment. Thanks! |
Greptile SummaryAdds comprehensive table and text formatting capabilities to the Feishu document tool. The changes are well-structured across focused helper modules and integrate cleanly with the existing Key improvements:
Architecture:
Code quality observations:
Confidence Score: 4/5
Last reviewed commit: e3829d2 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e3829d20c4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9eeeae879c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if ( | ||
| currentBatch.blocks.length + newBlocks.length > BATCH_SIZE && | ||
| currentBatch.blocks.length > 0 | ||
| ) { |
There was a problem hiding this comment.
Split oversized single-subtree descendant batches
When one first-level block expands to more than 1000 descendants (for example, a very large markdown table), this batching condition never splits it because it only rolls over when currentBatch.blocks.length > 0. That allows a single request with descendants.length > 1000 to reach documentBlockDescendant.create, which violates the API limit and makes large docs fail even though batching is enabled. Add handling for newBlocks.length > BATCH_SIZE so a single subtree is chunked (or rejected explicitly) instead of being sent as one oversized batch.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e22e65833c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11052673c7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (!url && !filePath) { | ||
| throw new Error("Either url or file_path is required"); | ||
| throw new Error("Either url, file_path, or image (base64/data URI) must be provided"); | ||
| } | ||
| if (url && filePath) { | ||
| throw new Error("Provide only one of url or file_path"); |
There was a problem hiding this comment.
Reject mixed upload_image input sources
resolveUploadInput now accepts image, but the exclusivity check only guards url vs file_path; when callers send image together with either of those fields, the function silently takes the image branch and ignores the other source instead of failing fast. This can upload the wrong asset for requests that accidentally include multiple inputs (for example, adapters that always populate url while adding image fallback), so this path should enforce exactly one of url/file_path/image.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in f071968. Added a mutual-exclusivity check at the top of resolveUploadInput: if more than one of url, file_path, or image is non-empty, it throws immediately with a list of which sources were provided rather than silently preferring image.
| if (imageInput) { | ||
| const buffer = Buffer.from(imageInput.trim(), "base64"); | ||
| if (buffer.length > maxBytes) { | ||
| throw new Error(`Base64 image exceeds limit: ${buffer.length} bytes > ${maxBytes} bytes`); | ||
| } | ||
| return { buffer, fileName: explicitFileName ?? "image.png" }; |
There was a problem hiding this comment.
Validate base64 payloads before treating them as images
The plain-base64 branch decodes with Buffer.from(imageInput.trim(), "base64") and accepts the result without validating that the input was actually base64; Node's decoder is permissive, so malformed strings decode into arbitrary bytes instead of throwing. That means invalid image values can proceed as apparently valid uploads and only fail later with opaque Feishu errors, so this should reject non-base64/empty decodes up front.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in f071968. The plain-base64 branch now validates the trimmed input against /^[A-Za-z0-9+/]+=*$/ before decoding; strings with out-of-alphabet characters throw a clear error directing callers to use a data URI or file path. An additional guard rejects empty-buffer results after decode.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f071968e5c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // dropped, corrupting the surrounding text. The closing tag name is not | ||
| // validated against the opening tag: [red]text[/green] is treated as | ||
| // [red]text[/red] — opening tag style applies, closing tag is consumed. | ||
| const tagPattern = /\[([^\]]+)\](.*?)\[\/(?:[^\]]+)\]|([^[]+|\[)/gs; |
There was a problem hiding this comment.
Treat unknown bracket tags as plain text before parsing
parseColorMarkup accepts any [tag]...[/anything] pair, so an ordinary bracket token can consume a later real closing tag and corrupt formatting. For example, Revenue [Q1] up [red]+5%[/red] is parsed as one segment with tag Q1, which swallows [/red] and prevents the red style from being applied. This silently rewrites user text whenever non-markup brackets appear before a styled span, so unknown/opening tags should be rejected as markup (or require a matching closing tag name) and emitted literally.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 0958c4b. The opening tag in the regex is now constrained to the set of recognised names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray). Unknown tokens like [Q1] no longer match the tag alternative and are emitted as plain text through the fallback branches, leaving any subsequent [/red] available for its actual styled span.
| `Use a data URI (data:image/png;base64,...) or a local file path instead.`, | ||
| ); | ||
| } | ||
| const buffer = Buffer.from(trimmed, "base64"); |
There was a problem hiding this comment.
Enforce upload_image byte limit before base64 decode
The image branches decode base64 with Buffer.from(...) and only then compare buffer.length to maxBytes, which means very large base64 inputs can allocate far beyond the configured media limit before rejection. In practice, a malicious or accidental oversized upload_image payload can spike memory or crash the plugin process even though a limit is configured. Compute/validate the decoded size from the input length first (for both plain base64 and data-URI payloads) before allocating the buffer.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 0958c4b. Both the data-URI branch and the plain-base64 branch now compute ceil(base64_length * 3 / 4) and compare that estimate against maxBytes before calling Buffer.from(), so oversized payloads are rejected before any allocation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0958c4b208
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 505ca0dd6c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const buffer = Buffer.from(trimmedData, "base64"); | ||
| return { buffer, fileName: explicitFileName ?? `image.${ext}` }; |
There was a problem hiding this comment.
Reject malformed data-URI base64 that decodes to empty bytes
Fresh evidence: even with the alphabet check, payloads like data:image/png;base64,A=== satisfy ^[A-Za-z0-9+/]+=*$, decode to an empty buffer in Node, and are returned here as a valid upload input. Because this branch immediately returns after Buffer.from(...) (without the empty-buffer guard used in the plain-base64 branch), malformed image data URIs are accepted and only fail later with opaque downstream upload errors.
Useful? React with 👍 / 👎.
… image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID)
write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected.
- resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name.
P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text.
P2 - Throw ENOENT for non-existing absolute image paths (docx.ts):
Previously a non-existing absolute path like /tmp/missing.png fell
through to Buffer.from(..., 'base64') and uploaded garbage bytes.
Now throws a descriptive ENOENT error and hints at data URI format
for callers intending to pass JPEG binary data (which starts with /9j/).
P2 - Fail clearly when insert anchor block is not found (docx.ts):
insertDoc previously set insertIndex to -1 (append) when after_block_id
was absent from the parent's child list, silently inserting at the wrong
position. Two fixes:
1. Paginate through all children (documentBlockChildren.get returns up to
200 per page) before searching for the anchor.
2. Throw a descriptive error if after_block_id is still not found after
full pagination, instead of silently falling back to append.
- Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty.
- parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch.
- docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors.
505ca0d to
17996d3
Compare
|
Thank you for the feature work and patience during the follow-up pass. Merged in #29411 via squash at 0740fb8. What was validated before merge:
Follow-up aligned tests/changelog with the descendant insertion path and preserved the image-input hardening behavior in Credit remains with the original implementation and PR author; this was a merge-prep safety pass only. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 17996d3665
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| blocks.length > BATCH_SIZE | ||
| ? await insertBlocksInBatches(client, docToken, sortedBlocks, firstLevelBlockIds, logger) | ||
| : await insertBlocksWithDescendant(client, docToken, sortedBlocks, firstLevelBlockIds); | ||
| const imagesProcessed = await processImages(client, docToken, markdown, inserted, maxBytes); |
There was a problem hiding this comment.
Patch image tokens for descendant-only image blocks
After switching write/append/insert to documentBlockDescendant.create, this flow passes only res.data.children into processImages, but that response contains direct children of the insertion parent, not deeper descendants. In markdown that now produces nested structures (for example, images inside a table cell or list subtree), the image blocks are descendants and never appear in inserted, so processImages skips them and the document keeps unresolved image placeholders. Collect descendant image block IDs (or query inserted subtree blocks) before calling processImages.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in follow-up commit e729228 (feat/feishu-extended-doc-ops). Added collectAllDescendantBlocks() which BFS-expands the inserted top-level blocks via documentBlockChildren.get to collect the full subtree before passing to processImages. Image blocks (type 27) are excluded from the BFS queue since they are leaf nodes. A follow-up PR will be opened to track this fix.
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* main: (31 commits) fix(browser): resolve correct targetId in navigate response after renderer swap (openclaw#25326) fix: sed escaping and UID mismatch in Podman Quadlet setup (openclaw#26414) fix(cron): pass heartbeat target=last for main-session cron jobs (openclaw#28508) (openclaw#28583) fix(cron): disable messaging tool when delivery.mode is none (openclaw#21808) (openclaw#21896) fix: clear delivery routing state when creating isolated cron sessions (openclaw#27778) fix(cron): avoid marking queued announce paths as delivered (openclaw#29716) fix(cron): enable completion direct send for text-only announce delivery (openclaw#29151) fix(cron): force main-target system events onto main session (openclaw#28898) fix(cron): condition requireExplicitMessageTarget on resolved delivery (openclaw#28017) feat(cron): add --account flag for multi-account delivery routing (openclaw#26284) fix: schedule nextWakeAtMs for isolated sessionTarget cron jobs (openclaw#19541) fix: sandbox browser docker no-sandbox rollout (openclaw#29879) (thanks @Lukavyi) GitHub: add regression bug issue template and routing (openclaw#29864) thanks @Takhoffman feat(feishu): add chat info/member tool (openclaw#14674) feat(feishu): add markdown tables, positional insert, color_text, and table ops (openclaw#29411) feat(feishu): add parent/root inbound context for quote support (openclaw#18529) fix: land android onboarding and voice reliability updates (openclaw#29796) fix(android-voice): rotate playback token per assistant reply fix(android-voice): retry talk config after transient failures fix(android-voice): cancel in-flight speech when speaker muted ...
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> (cherry picked from commit 0740fb8) # Conflicts: # CHANGELOG.md # extensions/feishu/src/docx.ts
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> (cherry picked from commit 0740fb8) # Conflicts: # CHANGELOG.md
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> (cherry picked from commit 0740fb8)
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
… table ops (openclaw#29411) * feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes Extends feishu_doc on top of openclaw#20304 with capabilities that are not yet covered: Markdown → native table rendering: - write/append now use the Descendant API instead of Children API, enabling GFM markdown tables (block_type 31/32) to render as native Feishu tables automatically - Adaptive column widths calculated from cell content (CJK chars 2x weight) - Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts) New actions: - insert: positional markdown insertion after a given block_id - color_text: apply color/bold to a text block via [red]...[/red] markup - insert_table_row / insert_table_column: add rows or columns to a table - delete_table_rows / delete_table_columns: remove rows or columns - merge_table_cells: merge a rectangular cell range Image upload fixes (affects write, append, and upload_image): - upload_image now accepts data URI and plain base64 in addition to url/file_path, covering DALL-E b64_json, canvas screenshots, etc. - Fix: pass Buffer directly to drive.media.uploadAll instead of Readable.from(), which caused Content-Length mismatch for large images - Fix: same Readable bug fixed in upload_file - Fix: pass drive_route_token via extra field for correct multi-datacenter routing (per API docs: required when parent_node is a document block ID) * fix(feishu): add documentBlockDescendant mock to docx.test.ts write/append now use the Descendant API (documentBlockDescendant.create) instead of Children API. The existing test mock was missing this SDK method, causing processImages to never be reached and fetchRemoteMedia to go uncalled. Added blockDescendantCreateMock returning an image block so the 'skips image upload when markdown image URL is blocked' test flows through processImages as expected. * fix(feishu): address bot review feedback - resolveUploadInput: remove length < 1024 guard on file path detection. Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish file paths from base64 strings at any length. The old guard caused file paths ≥1024 chars to fall through to the base64 branch incorrectly. - parseColorMarkup: add comment clarifying that mismatched closing tags (e.g. [red]text[/green]) are intentional — opening tag style is applied, closing tag is consumed regardless of name. * fix(feishu): address second-round codex bot review feedback P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts): A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot be split atomically (e.g. a very large table). Previously such a block was silently added to the current batch and sent as an oversized request, violating the API limit. Now throws a descriptive error so callers know to reduce the content size. P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts): Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]' closer. The original regex dropped the '[' character in this case, silently corrupting the text. Fixed by appending '|\[' to the plain-text alternative so any '[' that does not open a complete tag is captured as literal text. * fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. * fix(feishu): address fourth-round codex bot review feedback - Enforce mutual exclusivity across all three upload sources (url, file_path, image): throw immediately when more than one is provided, instead of silently preferring the image branch and ignoring the others. - Validate plain base64 payloads before decoding: reject strings that contain characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that malformed inputs fail fast with a clear error rather than decoding to garbage bytes and producing an opaque Feishu API failure downstream. Also throw if the decoded buffer is empty. * fix(feishu): address fifth-round codex bot review feedback - parseColorMarkup: restrict opening tag regex to known colour/style names (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that ordinary bracket tokens like [Q1] can no longer consume a subsequent real closing tag ([/red]) and corrupt the surrounding styled spans. Unknown tags now fall through to the plain-text alternatives and are emitted literally. - resolveUploadInput: estimate decoded byte count from base64 input length (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized payloads from spiking memory before the maxBytes limit is enforced. Applies to both the data-URI branch and the plain-base64 branch. * fix(feishu): address sixth-round codex bot review feedback - docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table branch so tables with 15+ columns don't produce sub-50 widths that Feishu rejects as invalid column_width values. - docx.ts (data URI branch): validate the ';base64' marker before decoding so plain/URL-encoded data URIs are rejected with a clear error; also validate the payload against the base64 alphabet (same guard already applied in the plain-base64 branch) so malformed inputs fail fast rather than producing opaque downstream Feishu errors. * Feishu: align docx descendant insertion tests and changelog --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Changes
Markdown → native table rendering
writeandappendnow use the Descendant API instead of Children API, so GFM markdown tables render as native Feishu tables automatically — no manualcreate_tablecall needed:Column widths are calculated from cell content length (CJK chars weighted 2x). Documents with >1000 blocks are automatically batched.
New actions
insertcolor_text[red]text[/red]markupinsert_table_row/insert_table_columndelete_table_rows/delete_table_columnsmerge_table_cellsupload_imageextended input formatsAdds
imagefield to accept data URI or plain base64 in addition tourl/file_path, covering DALL-Eb64_jsonresponses, canvas screenshots, and matplotlib outputs.Image upload fixes
Two bugs that affect
write,append, andupload_image:Readable.from(buf)produces a stream with unknown length, causing form-data to send a wrongContent-Length. Fixed by passingBufferdirectly. Same fix applied toupload_file.parent_nodeis a document block ID, the API requiresextra: { drive_route_token: docToken }. Per the API docs, certain upload scenarios require providing the token of the cloud document that the block belongs to.New helper files