feat(feishu): support Docx table create/write + image/file upload actions in feishu_doc#20304
Conversation
| for (let r = 0; r < writeRows; r++) { | ||
| const rowValues = values[r] ?? []; | ||
| const writeCols = Math.min(rowValues.length, cols); | ||
|
|
||
| for (let c = 0; c < writeCols; c++) { | ||
| const cellId = cellIds[r * cols + c]; | ||
| if (!cellId) continue; | ||
|
|
||
| // table cell is a container block: clear existing children, then create text child blocks | ||
| const childrenRes = await client.docx.documentBlockChildren.get({ | ||
| path: { document_id: docToken, block_id: cellId }, | ||
| }); | ||
| if (childrenRes.code !== 0) { | ||
| throw new Error(childrenRes.msg); | ||
| } | ||
|
|
||
| const existingChildren = childrenRes.data?.items ?? []; | ||
| if (existingChildren.length > 0) { | ||
| const delRes = await client.docx.documentBlockChildren.batchDelete({ | ||
| path: { document_id: docToken, block_id: cellId }, | ||
| data: { start_index: 0, end_index: existingChildren.length }, | ||
| }); | ||
| if (delRes.code !== 0) { | ||
| throw new Error(delRes.msg); | ||
| } | ||
| } | ||
|
|
||
| const text = rowValues[c] ?? ""; | ||
| const converted = await convertMarkdown(client, text); | ||
| const sorted = sortBlocksByFirstLevel(converted.blocks, converted.firstLevelBlockIds); | ||
|
|
||
| if (sorted.length > 0) { | ||
| await insertBlocks(client, docToken, sorted, cellId); | ||
| } | ||
|
|
||
| written++; | ||
| } |
There was a problem hiding this comment.
Sequential API calls per cell may cause timeouts on large tables
For each cell, writeTableCells makes 3–4 sequential API round-trips (get children, batch delete, convert markdown, insert blocks). For a moderately sized table (e.g. 10x10 = 100 cells), this results in 300–400 sequential API calls that could be slow or trigger Feishu rate limits.
Consider batching cell writes or at minimum adding a comment about expected performance characteristics so callers know to keep table sizes reasonable.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/feishu/src/docx.ts
Line: 427:463
Comment:
**Sequential API calls per cell may cause timeouts on large tables**
For each cell, `writeTableCells` makes 3–4 sequential API round-trips (get children, batch delete, convert markdown, insert blocks). For a moderately sized table (e.g. 10x10 = 100 cells), this results in 300–400 sequential API calls that could be slow or trigger Feishu rate limits.
Consider batching cell writes or at minimum adding a comment about expected performance characteristics so callers know to keep table sizes reasonable.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Great point, thank you. I added a runtime warning for large writes and documented the sequential-cell performance characteristic in the skill doc so callers can plan table sizes accordingly.
extensions/feishu/src/doc-schema.ts
Outdated
| row_size: Type.Number({ description: "Table row count", minimum: 1 }), | ||
| column_size: Type.Number({ description: "Table column count", minimum: 1 }), |
There was a problem hiding this comment.
Type.Integer would be more precise for row/column counts
row_size and column_size use Type.Number which allows fractional values like 1.5. Since these represent discrete table dimensions, Type.Integer would enforce whole numbers at the schema level rather than relying on the Feishu API to reject or truncate.
| row_size: Type.Number({ description: "Table row count", minimum: 1 }), | |
| column_size: Type.Number({ description: "Table column count", minimum: 1 }), | |
| row_size: Type.Integer({ description: "Table row count", minimum: 1 }), | |
| column_size: Type.Integer({ description: "Table column count", minimum: 1 }), |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/feishu/src/doc-schema.ts
Line: 51:52
Comment:
**`Type.Integer` would be more precise for row/column counts**
`row_size` and `column_size` use `Type.Number` which allows fractional values like `1.5`. Since these represent discrete table dimensions, `Type.Integer` would enforce whole numbers at the schema level rather than relying on the Feishu API to reject or truncate.
```suggestion
row_size: Type.Integer({ description: "Table row count", minimum: 1 }),
column_size: Type.Integer({ description: "Table column count", minimum: 1 }),
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Thanks for the suggestion — updated to Type.Integer for row_size/column_size in both table actions.
|
Thanks for the reviews and CI signal. I’ve checked the thread and will push a follow-up shortly to address:
I’ll update this PR again after the fix is pushed and checks rerun. |
|
Follow-up: for clarity, I mean using Type.Integer for row_size/column_size in the schema. |
|
Thanks for the review feedback. I’ve pushed a follow-up commit that:
I also ran |
|
Quick status update: the remaining red checks appear unrelated to this PR diff. Current failures are in bluebubbles test harness on macOS/Windows ( All Feishu/doc-related checks are green on this head commit. If maintainers agree this is transient/unrelated infra/test flake, could someone with permissions rerun/override so this can merge? Happy to adjust anything needed on my side. |
|
Correction (shell escaped my previous comment): CI blockers are unchanged and appear unrelated to this PR diff — lanes: "macos" and "checks-windows (node, test, pnpm canvas:a2ui:bundle && pnpm test)" with bluebubbles test-harness failure. I also tried rerun-failed-jobs, but this account lacks required admin permission (HTTP 403). If a maintainer reruns/overrides those unrelated lanes, this PR should be merge-ready from the Feishu-doc scope. |
|
Hourly follow-up: CI is still blocked only by the same apparently unrelated failures: macos, and checks-windows (node, test, pnpm canvas:a2ui:bundle && pnpm test), with bluebubbles test-harness error at extensions/bluebubbles/src/test-harness.ts:43. Feishu/Docx-related checks on this PR are green. Could a maintainer with permissions please rerun failed jobs (or override if appropriate) so this can proceed to merge? Thank you 🙏 |
|
Correction (shell escaped previous message): Follow-up pushed on this PR branch to add feishu_doc actions "upload_image" and "upload_file" for Docx blocks. Supports either remote "url" or local "file_path", with optional "filename" and "parent_block_id".\n\nValidation done: added and ran a unit test for local-path upload flow in extensions/feishu/src/docx.test.ts; command |
|
I investigated the latest macOS failure. It is in BlueBubbles tests and was introduced by my temporary test-harness tweak, not by the Feishu Docx feature itself. I have now reverted that tweak in commit a0106e2 to restore baseline behavior, and retriggered CI on this PR branch. |
27eef07 to
ae6b30b
Compare
47273be to
b5a4d95
Compare
1e5724a to
ea37b7d
Compare
a4c1eb2 to
afc21cd
Compare
… 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>
…ions in feishu_doc (openclaw#20304) 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>
…ions in feishu_doc (openclaw#20304) 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>
…ions in feishu_doc (openclaw#20304) Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> (cherry picked from commit 56fa058) # Conflicts: # 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> (cherry picked from commit 0740fb8) # Conflicts: # CHANGELOG.md # extensions/feishu/src/docx.ts
…ions in feishu_doc (openclaw#20304) 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>
…ions in feishu_doc (openclaw#20304) 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>
…ions in feishu_doc (openclaw#20304) 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>
…ions in feishu_doc (openclaw#20304) 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>
…ions in feishu_doc (openclaw#20304) Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…ions in feishu_doc (openclaw#20304) 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>
…ions in feishu_doc (openclaw#20304) 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>
…ions in feishu_doc (openclaw#20304) Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> (cherry picked from commit 56fa058) # Conflicts: # extensions/feishu/skills/feishu-doc/SKILL.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> (cherry picked from commit 0740fb8) # Conflicts: # CHANGELOG.md
…ions in feishu_doc (openclaw#20304) 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>
…ions in feishu_doc (openclaw#20304) Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> (cherry picked from commit 56fa058)
… 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)
…ions in feishu_doc (openclaw#20304) 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>
Summary
feishu_doccould not create regular Docx table blocks through the tool API.create_table,write_table_cells, andcreate_table_with_valuesactions tofeishu_doc.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
feishu_docnow supports:create_tablefor Docx table block creationwrite_table_cellsfor filling existing table cellscreate_table_with_valuesfor one-step create+fillSecurity Impact (required)
Yes/No) NoYes/No) NoYes/No) NoYes/No) Yes (new tool actions in existing tool)Yes/No) NoYes, explain risk + mitigation:feishu_doccould be misused with malformed parameters.Repro + Verification
Environment
Steps
feishu_docwithaction=create_tableon a Docx doc token.feishu_docwithaction=write_table_cellsand 2D values.action=create_table_with_valuesand verify one-step success.Expected
Actual
Evidence
Snippet (local runtime):
create_table_with_values→{ success: true, table_block_id: "...", cells_written: 4 }Human Verification (required)
What I personally verified (not just CI), and how:
pnpm build && pnpm check && pnpm teston upstream repo in this environment.Compatibility / Migration
Yes/No) YesYes/No) NoYes/No) NoFailure Recovery (if this breaks)
docx.tsand schema entries indoc-schema.ts.extensions/feishu/src/docx.tsextensions/feishu/src/doc-schema.tsextensions/feishu/skills/feishu-doc/SKILL.mdRisks and Mitigations
AI-assisted: Yes (implementation + drafting assisted by AI tooling). The code paths were manually reviewed and runtime-tested.
Greptile Summary
Adds three new Docx table actions (
create_table,write_table_cells,create_table_with_values) to thefeishu_doctool, enabling programmatic table creation and cell population in Feishu documents.createTablefunction creates table blocks directly via the FeishudocumentBlockChildren.createAPI with configurable row/column sizes and optional column widthswriteTableCellsfunction fetches table metadata, iterates cells in row-major order, clears existing content, and inserts converted markdown into each cellcreateTableWithValuescomposes the two above for a one-step create-and-fill workflowdoc-schema.tswith proper validation constraints (minimum: 1,minItems: 1)references/block-types.mdstill states "Table blocks CANNOT be created via thedocumentBlockChildren.createAPI" — this is now outdated given the newcreate_tableactionConfidence Score: 5/5
Type.Numberinstead ofType.Integerfor row/col counts is imprecise but consistent with existing patterns.extensions/feishu/src/docx.ts— review the sequential API call pattern inwriteTableCellsfor performance with large tables.Last reviewed commit: c295e09
(2/5) Greptile learns from your feedback when you react with thumbs up/down!