diff --git a/extensions/feishu/src/doc-schema.ts b/extensions/feishu/src/doc-schema.ts index ab657065a69..b3ecf4975ea 100644 --- a/extensions/feishu/src/doc-schema.ts +++ b/extensions/feishu/src/doc-schema.ts @@ -1,12 +1,34 @@ import { Type, type Static } from "@sinclair/typebox"; +const FEISHU_DOC_ACTION_VALUES = [ + "read", + "write", + "append", + "insert", + "create", + "list_blocks", + "get_block", + "update_block", + "delete_block", + "create_table", + "write_table_cells", + "create_table_with_values", + "insert_table_row", + "insert_table_column", + "delete_table_rows", + "delete_table_columns", + "merge_table_cells", + "upload_image", + "upload_file", + "color_text", +] as const; + const tableCreationProperties = { - doc_token: Type.String({ description: "Document token" }), parent_block_id: Type.Optional( Type.String({ description: "Parent block ID (default: document root)" }), ), - row_size: Type.Integer({ description: "Table row count", minimum: 1 }), - column_size: Type.Integer({ description: "Table column count", minimum: 1 }), + row_size: Type.Optional(Type.Integer({ description: "Table row count", minimum: 1 })), + column_size: Type.Optional(Type.Integer({ description: "Table column count", minimum: 1 })), column_width: Type.Optional( Type.Array(Type.Number({ minimum: 1 }), { description: "Column widths in px (length should match column_size)", @@ -14,169 +36,103 @@ const tableCreationProperties = { ), }; -export const FeishuDocSchema = Type.Union([ - Type.Object({ - action: Type.Literal("read"), - doc_token: Type.String({ description: "Document token (extract from URL /docx/XXX)" }), - }), - Type.Object({ - action: Type.Literal("write"), - doc_token: Type.String({ description: "Document token" }), - content: Type.String({ - description: "Markdown content to write (replaces entire document content)", +export const FeishuDocSchema = Type.Object( + { + action: Type.Unsafe<(typeof FEISHU_DOC_ACTION_VALUES)[number]>({ + type: "string", + enum: [...FEISHU_DOC_ACTION_VALUES], + description: + "Document action to run: read, write, append, insert, create, list_blocks, get_block, update_block, delete_block, create_table, write_table_cells, create_table_with_values, insert_table_row, insert_table_column, delete_table_rows, delete_table_columns, merge_table_cells, upload_image, upload_file, color_text", }), - }), - Type.Object({ - action: Type.Literal("append"), - doc_token: Type.String({ description: "Document token" }), - content: Type.String({ description: "Markdown content to append to end of document" }), - }), - Type.Object({ - action: Type.Literal("insert"), - doc_token: Type.String({ description: "Document token" }), - content: Type.String({ description: "Markdown content to insert" }), - after_block_id: Type.String({ - description: "Insert content after this block ID. Use list_blocks to find block IDs.", - }), - }), - Type.Object({ - action: Type.Literal("create"), - title: Type.String({ description: "Document title" }), - folder_token: Type.Optional(Type.String({ description: "Target folder token (optional)" })), + doc_token: Type.Optional( + Type.String({ + description: + "Document token. Required for all actions except create. Extract from URL /docx/XXX.", + }), + ), + content: Type.Optional( + Type.String({ + description: + "Markdown or text payload. Required for write, append, insert, update_block, and color_text.", + }), + ), + after_block_id: Type.Optional( + Type.String({ + description: "Required for insert. Insert content after this block ID.", + }), + ), + title: Type.Optional( + Type.String({ + description: "Required for create and rename-style content creation flows.", + }), + ), + folder_token: Type.Optional(Type.String({ description: "Optional target folder for create." })), grant_to_requester: Type.Optional( Type.Boolean({ description: - "Grant edit permission to the trusted requesting Feishu user from runtime context (default: true).", + "For create, grant edit permission to the trusted requesting Feishu user from runtime context.", + }), + ), + block_id: Type.Optional( + Type.String({ + description: + "Block ID. Required for get_block, update_block, delete_block, table row/column operations, merge_table_cells, and color_text.", }), ), - }), - Type.Object({ - action: Type.Literal("list_blocks"), - doc_token: Type.String({ description: "Document token" }), - }), - Type.Object({ - action: Type.Literal("get_block"), - doc_token: Type.String({ description: "Document token" }), - block_id: Type.String({ description: "Block ID (from list_blocks)" }), - }), - Type.Object({ - action: Type.Literal("update_block"), - doc_token: Type.String({ description: "Document token" }), - block_id: Type.String({ description: "Block ID (from list_blocks)" }), - content: Type.String({ description: "New text content" }), - }), - Type.Object({ - action: Type.Literal("delete_block"), - doc_token: Type.String({ description: "Document token" }), - block_id: Type.String({ description: "Block ID" }), - }), - // Table creation (explicit structure) - Type.Object({ - action: Type.Literal("create_table"), ...tableCreationProperties, - }), - Type.Object({ - action: Type.Literal("write_table_cells"), - doc_token: Type.String({ description: "Document token" }), - table_block_id: Type.String({ description: "Table block ID" }), - values: Type.Array(Type.Array(Type.String()), { - description: "2D matrix values[row][col] to write into table cells", - minItems: 1, - }), - }), - Type.Object({ - action: Type.Literal("create_table_with_values"), - ...tableCreationProperties, - values: Type.Array(Type.Array(Type.String()), { - description: "2D matrix values[row][col] to write into table cells", - minItems: 1, - }), - }), - // Table row/column manipulation - Type.Object({ - action: Type.Literal("insert_table_row"), - doc_token: Type.String({ description: "Document token" }), - block_id: Type.String({ description: "Table block ID" }), + table_block_id: Type.Optional( + Type.String({ description: "Table block ID. Required for write_table_cells." }), + ), + values: Type.Optional( + Type.Array(Type.Array(Type.String()), { + description: + "2D matrix values[row][col]. Required for write_table_cells and create_table_with_values.", + minItems: 1, + }), + ), row_index: Type.Optional( - Type.Number({ description: "Row index to insert at (-1 for end, default: -1)" }), + Type.Number({ description: "Optional row index for insert_table_row (-1 for end)." }), ), - }), - Type.Object({ - action: Type.Literal("insert_table_column"), - doc_token: Type.String({ description: "Document token" }), - block_id: Type.String({ description: "Table block ID" }), column_index: Type.Optional( - Type.Number({ description: "Column index to insert at (-1 for end, default: -1)" }), + Type.Number({ + description: "Optional column index for insert_table_column (-1 for end).", + }), ), - }), - Type.Object({ - action: Type.Literal("delete_table_rows"), - doc_token: Type.String({ description: "Document token" }), - block_id: Type.String({ description: "Table block ID" }), - row_start: Type.Number({ description: "Start row index (0-based)" }), - row_count: Type.Optional(Type.Number({ description: "Number of rows to delete (default: 1)" })), - }), - Type.Object({ - action: Type.Literal("delete_table_columns"), - doc_token: Type.String({ description: "Document token" }), - block_id: Type.String({ description: "Table block ID" }), - column_start: Type.Number({ description: "Start column index (0-based)" }), - column_count: Type.Optional( - Type.Number({ description: "Number of columns to delete (default: 1)" }), + row_start: Type.Optional( + Type.Number({ description: "Start row index. Required for delete/merge row actions." }), + ), + row_count: Type.Optional(Type.Number({ description: "Rows to delete (default: 1)." })), + row_end: Type.Optional( + Type.Number({ description: "End row index (exclusive). Required for merge_table_cells." }), + ), + column_start: Type.Optional( + Type.Number({ description: "Start column index. Required for delete/merge column actions." }), + ), + column_count: Type.Optional(Type.Number({ description: "Columns to delete (default: 1)." })), + column_end: Type.Optional( + Type.Number({ + description: "End column index (exclusive). Required for merge_table_cells.", + }), + ), + url: Type.Optional(Type.String({ description: "Remote file/image URL for upload actions." })), + file_path: Type.Optional( + Type.String({ description: "Local file path for upload_image or upload_file." }), ), - }), - Type.Object({ - action: Type.Literal("merge_table_cells"), - doc_token: Type.String({ description: "Document token" }), - block_id: Type.String({ description: "Table block ID" }), - row_start: Type.Number({ description: "Start row index" }), - row_end: Type.Number({ description: "End row index (exclusive)" }), - column_start: Type.Number({ description: "Start column index" }), - column_end: Type.Number({ description: "End column index (exclusive)" }), - }), - // Image / file upload - Type.Object({ - action: Type.Literal("upload_image"), - doc_token: Type.String({ description: "Document token" }), - url: Type.Optional(Type.String({ description: "Remote image URL (http/https)" })), - file_path: Type.Optional(Type.String({ description: "Local image file path" })), image: Type.Optional( Type.String({ description: - "Image as data URI (data:image/png;base64,...) or plain base64 string. Use instead of url/file_path for DALL-E outputs, canvas screenshots, etc.", + "Image as data URI or base64 string for upload_image when no URL/file_path is used.", }), ), - parent_block_id: Type.Optional( - Type.String({ description: "Parent block ID (default: document root)" }), - ), - filename: Type.Optional(Type.String({ description: "Optional filename override" })), + filename: Type.Optional(Type.String({ description: "Optional upload filename override." })), index: Type.Optional( Type.Integer({ minimum: 0, - description: "Insert position (0-based index among siblings). Omit to append.", + description: "Optional insert position among siblings for upload_image.", }), ), - }), - Type.Object({ - action: Type.Literal("upload_file"), - doc_token: Type.String({ description: "Document token" }), - url: Type.Optional(Type.String({ description: "Remote file URL (http/https)" })), - file_path: Type.Optional(Type.String({ description: "Local file path" })), - parent_block_id: Type.Optional( - Type.String({ description: "Parent block ID (default: document root)" }), - ), - filename: Type.Optional(Type.String({ description: "Optional filename override" })), - }), - // Text color / style - Type.Object({ - action: Type.Literal("color_text"), - doc_token: Type.String({ description: "Document token" }), - block_id: Type.String({ description: "Text block ID to update" }), - content: Type.String({ - description: - 'Text with color markup. Tags: [red], [green], [blue], [orange], [yellow], [purple], [grey], [bold], [bg:yellow]. Example: "Revenue [green]+15%[/green] YoY"', - }), - }), -]); + }, + { additionalProperties: false }, +); export type FeishuDocParams = Static; diff --git a/extensions/feishu/src/docx.account-selection.test.ts b/extensions/feishu/src/docx.account-selection.test.ts index 6ac1b9dbfa5..bc7b04debf9 100644 --- a/extensions/feishu/src/docx.account-selection.test.ts +++ b/extensions/feishu/src/docx.account-selection.test.ts @@ -67,4 +67,29 @@ describe("feishu_doc account selection", () => { expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-a"); }); + + test("registers provider-friendly feishu_doc parameters for embedded sessions", () => { + const cfg = createDocEnabledConfig(); + const { api, resolveTool } = createToolFactoryHarness(cfg); + registerFeishuDocTools(api); + + const docTool = resolveTool("feishu_doc", { agentAccountId: "a" }); + const schema = docTool.parameters as { + type?: unknown; + anyOf?: unknown; + properties?: Record; + required?: unknown; + }; + + expect(schema.type).toBe("object"); + expect(schema.anyOf).toBeUndefined(); + expect(schema.required).toEqual(["action"]); + expect(schema.properties?.action?.enum).toEqual( + expect.arrayContaining(["read", "create", "list_blocks", "upload_image"]), + ); + expect(schema.properties?.action?.description).toEqual(expect.stringContaining("create_table")); + expect(schema.properties?.doc_token?.description).toEqual( + expect.stringContaining("Required for all actions except create"), + ); + }); }); diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index 7debd446a14..8a78b5c043d 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -33,6 +33,13 @@ function json(data: unknown) { }; } +function requireParam(value: string | undefined, label: string): string { + if (value) { + return value; + } + throw new Error(`${label} is required for this action.`); +} + /** Extract image URLs from markdown content */ function extractImageUrls(markdown: string): string[] { const regex = /!\[[^\]]*\]\(([^)]+)\)/g; @@ -1276,13 +1283,13 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { const client = getClient(p, defaultAccountId); switch (p.action) { case "read": - return json(await readDoc(client, p.doc_token)); + return json(await readDoc(client, requireParam(p.doc_token, "doc_token"))); case "write": return json( await writeDoc( client, - p.doc_token, - p.content, + requireParam(p.doc_token, "doc_token"), + requireParam(p.content, "content"), getMediaMaxBytes(p, defaultAccountId), api.logger, ), @@ -1291,8 +1298,8 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { return json( await appendDoc( client, - p.doc_token, - p.content, + requireParam(p.doc_token, "doc_token"), + requireParam(p.content, "content"), getMediaMaxBytes(p, defaultAccountId), api.logger, ), @@ -1301,9 +1308,9 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { return json( await insertDoc( client, - p.doc_token, - p.content, - p.after_block_id, + requireParam(p.doc_token, "doc_token"), + requireParam(p.content, "content"), + requireParam(p.after_block_id, "after_block_id"), getMediaMaxBytes(p, defaultAccountId), api.logger, ), @@ -1316,18 +1323,37 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { }), ); case "list_blocks": - return json(await listBlocks(client, p.doc_token)); + return json(await listBlocks(client, requireParam(p.doc_token, "doc_token"))); case "get_block": - return json(await getBlock(client, p.doc_token, p.block_id)); + return json( + await getBlock( + client, + requireParam(p.doc_token, "doc_token"), + requireParam(p.block_id, "block_id"), + ), + ); case "update_block": - return json(await updateBlock(client, p.doc_token, p.block_id, p.content)); + return json( + await updateBlock( + client, + requireParam(p.doc_token, "doc_token"), + requireParam(p.block_id, "block_id"), + requireParam(p.content, "content"), + ), + ); case "delete_block": - return json(await deleteBlock(client, p.doc_token, p.block_id)); + return json( + await deleteBlock( + client, + requireParam(p.doc_token, "doc_token"), + requireParam(p.block_id, "block_id"), + ), + ); case "create_table": return json( await createTable( client, - p.doc_token, + requireParam(p.doc_token, "doc_token"), p.row_size, p.column_size, p.parent_block_id, @@ -1336,13 +1362,18 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { ); case "write_table_cells": return json( - await writeTableCells(client, p.doc_token, p.table_block_id, p.values), + await writeTableCells( + client, + requireParam(p.doc_token, "doc_token"), + requireParam(p.table_block_id, "table_block_id"), + p.values, + ), ); case "create_table_with_values": return json( await createTableWithValues( client, - p.doc_token, + requireParam(p.doc_token, "doc_token"), p.row_size, p.column_size, p.values, @@ -1354,7 +1385,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { return json( await uploadImageBlock( client, - p.doc_token, + requireParam(p.doc_token, "doc_token"), getMediaMaxBytes(p, defaultAccountId), p.url, p.file_path, @@ -1368,7 +1399,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { return json( await uploadFileBlock( client, - p.doc_token, + requireParam(p.doc_token, "doc_token"), getMediaMaxBytes(p, defaultAccountId), p.url, p.file_path, @@ -1377,19 +1408,38 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { ), ); case "color_text": - return json(await updateColorText(client, p.doc_token, p.block_id, p.content)); + return json( + await updateColorText( + client, + requireParam(p.doc_token, "doc_token"), + requireParam(p.block_id, "block_id"), + requireParam(p.content, "content"), + ), + ); case "insert_table_row": - return json(await insertTableRow(client, p.doc_token, p.block_id, p.row_index)); + return json( + await insertTableRow( + client, + requireParam(p.doc_token, "doc_token"), + requireParam(p.block_id, "block_id"), + p.row_index, + ), + ); case "insert_table_column": return json( - await insertTableColumn(client, p.doc_token, p.block_id, p.column_index), + await insertTableColumn( + client, + requireParam(p.doc_token, "doc_token"), + requireParam(p.block_id, "block_id"), + p.column_index, + ), ); case "delete_table_rows": return json( await deleteTableRows( client, - p.doc_token, - p.block_id, + requireParam(p.doc_token, "doc_token"), + requireParam(p.block_id, "block_id"), p.row_start, p.row_count, ), @@ -1398,8 +1448,8 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { return json( await deleteTableColumns( client, - p.doc_token, - p.block_id, + requireParam(p.doc_token, "doc_token"), + requireParam(p.block_id, "block_id"), p.column_start, p.column_count, ), @@ -1408,8 +1458,8 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) { return json( await mergeTableCells( client, - p.doc_token, - p.block_id, + requireParam(p.doc_token, "doc_token"), + requireParam(p.block_id, "block_id"), p.row_start, p.row_end, p.column_start, diff --git a/extensions/feishu/src/tool-account-routing.test.ts b/extensions/feishu/src/tool-account-routing.test.ts index 6cc9172de3e..079b0825e48 100644 --- a/extensions/feishu/src/tool-account-routing.test.ts +++ b/extensions/feishu/src/tool-account-routing.test.ts @@ -69,6 +69,31 @@ describe("feishu tool account routing", () => { expect(createFeishuClientMock.mock.calls.at(-1)?.[0]?.appId).toBe("app-b"); }); + test("wiki tool exposes object parameters with explicit action enum", () => { + const { api, resolveTool } = createToolFactoryHarness( + createConfig({ + toolsA: { wiki: true }, + }), + ); + registerFeishuWikiTools(api); + + const tool = resolveTool("feishu_wiki", { agentAccountId: "a" }); + const schema = tool.parameters as { + type?: unknown; + anyOf?: unknown; + properties?: Record; + required?: unknown; + }; + + expect(schema.type).toBe("object"); + expect(schema.anyOf).toBeUndefined(); + expect(schema.required).toEqual(["action"]); + expect(schema.properties?.action?.enum).toEqual( + expect.arrayContaining(["spaces", "nodes", "get", "create", "rename"]), + ); + expect(schema.properties?.action?.description).toEqual(expect.stringContaining("search")); + }); + test("wiki tool prefers configured defaultAccount over inherited default account context", async () => { const { api, resolveTool } = createToolFactoryHarness( createConfig({ diff --git a/extensions/feishu/src/tool-factory-test-harness.ts b/extensions/feishu/src/tool-factory-test-harness.ts index 4c564e25052..b6ff07a53a3 100644 --- a/extensions/feishu/src/tool-factory-test-harness.ts +++ b/extensions/feishu/src/tool-factory-test-harness.ts @@ -8,6 +8,8 @@ type ToolFactoryLike = (ctx: ToolContextLike) => AnyAgentTool | AnyAgentTool[] | export type ToolLike = { name: string; + description?: string; + parameters?: unknown; execute: (toolCallId: string, params: unknown) => Promise | unknown; }; @@ -30,6 +32,8 @@ function asToolLike(tool: AnyAgentTool, fallbackName?: string): ToolLike { } return { name, + description: candidate.description, + parameters: candidate.parameters, execute: (toolCallId, params) => execute(toolCallId, params), }; } diff --git a/extensions/feishu/src/wiki-schema.ts b/extensions/feishu/src/wiki-schema.ts index 006cc2da39d..db249e8cf20 100644 --- a/extensions/feishu/src/wiki-schema.ts +++ b/extensions/feishu/src/wiki-schema.ts @@ -1,55 +1,60 @@ import { Type, type Static } from "@sinclair/typebox"; -export const FeishuWikiSchema = Type.Union([ - Type.Object({ - action: Type.Literal("spaces"), - }), - Type.Object({ - action: Type.Literal("nodes"), - space_id: Type.String({ description: "Knowledge space ID" }), - parent_node_token: Type.Optional( - Type.String({ description: "Parent node token (optional, omit for root)" }), - ), - }), - Type.Object({ - action: Type.Literal("get"), - token: Type.String({ description: "Wiki node token (from URL /wiki/XXX)" }), - }), - Type.Object({ - action: Type.Literal("search"), - query: Type.String({ description: "Search query" }), - space_id: Type.Optional(Type.String({ description: "Limit search to this space (optional)" })), - }), - Type.Object({ - action: Type.Literal("create"), - space_id: Type.String({ description: "Knowledge space ID" }), - title: Type.String({ description: "Node title" }), - obj_type: Type.Optional( - Type.Union([Type.Literal("docx"), Type.Literal("sheet"), Type.Literal("bitable")], { - description: "Object type (default: docx)", +const FEISHU_WIKI_ACTION_VALUES = [ + "spaces", + "nodes", + "get", + "search", + "create", + "move", + "rename", +] as const; +const FEISHU_WIKI_OBJECT_TYPE_VALUES = ["docx", "sheet", "bitable"] as const; + +export const FeishuWikiSchema = Type.Object( + { + action: Type.Unsafe<(typeof FEISHU_WIKI_ACTION_VALUES)[number]>({ + type: "string", + enum: [...FEISHU_WIKI_ACTION_VALUES], + description: "Wiki action to run: spaces, nodes, get, search, create, move, rename", + }), + space_id: Type.Optional( + Type.String({ + description: "Knowledge space ID. Required for nodes, create, move, and rename.", }), ), parent_node_token: Type.Optional( - Type.String({ description: "Parent node token (optional, omit for root)" }), + Type.String({ + description: "Optional parent node token for nodes/create. Omit for the root level.", + }), + ), + token: Type.Optional( + Type.String({ description: "Wiki node token. Required for get." }), + ), + query: Type.Optional( + Type.String({ description: "Search query. Required for search." }), + ), + title: Type.Optional( + Type.String({ description: "Node title. Required for create and rename." }), + ), + obj_type: Type.Optional( + Type.Unsafe<(typeof FEISHU_WIKI_OBJECT_TYPE_VALUES)[number]>({ + type: "string", + enum: [...FEISHU_WIKI_OBJECT_TYPE_VALUES], + description: "Object type for create (default: docx).", + }), + ), + node_token: Type.Optional( + Type.String({ description: "Node token. Required for move and rename." }), ), - }), - Type.Object({ - action: Type.Literal("move"), - space_id: Type.String({ description: "Source knowledge space ID" }), - node_token: Type.String({ description: "Node token to move" }), target_space_id: Type.Optional( - Type.String({ description: "Target space ID (optional, same space if omitted)" }), + Type.String({ description: "Optional target space for move." }), ), target_parent_token: Type.Optional( - Type.String({ description: "Target parent node token (optional, root if omitted)" }), + Type.String({ description: "Optional target parent node token for move." }), ), - }), - Type.Object({ - action: Type.Literal("rename"), - space_id: Type.String({ description: "Knowledge space ID" }), - node_token: Type.String({ description: "Node token to rename" }), - title: Type.String({ description: "New title" }), - }), -]); + }, + { additionalProperties: false }, +); export type FeishuWikiParams = Static; diff --git a/extensions/feishu/src/wiki.ts b/extensions/feishu/src/wiki.ts index a2df89ff0fe..40b101f0036 100644 --- a/extensions/feishu/src/wiki.ts +++ b/extensions/feishu/src/wiki.ts @@ -17,6 +17,13 @@ const WIKI_ACCESS_HINT = "To grant wiki access: Open wiki space → Settings → Members → Add the bot. " + "See: https://open.feishu.cn/document/server-docs/docs/wiki-v2/wiki-qa#a40ad4ca"; +function requireParam(value: string | undefined, label: string): string { + if (value) { + return value; + } + throw new Error(`${label} is required for this action.`); +} + async function listSpaces(client: Lark.Client) { const res = await client.wiki.space.list({}); if (res.code !== 0) { @@ -192,9 +199,15 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) { case "spaces": return jsonToolResult(await listSpaces(client)); case "nodes": - return jsonToolResult(await listNodes(client, p.space_id, p.parent_node_token)); + return jsonToolResult( + await listNodes( + client, + requireParam(p.space_id, "space_id"), + p.parent_node_token, + ), + ); case "get": - return jsonToolResult(await getNode(client, p.token)); + return jsonToolResult(await getNode(client, requireParam(p.token, "token"))); case "search": return jsonToolResult({ error: @@ -202,20 +215,33 @@ export function registerFeishuWikiTools(api: OpenClawPluginApi) { }); case "create": return jsonToolResult( - await createNode(client, p.space_id, p.title, p.obj_type, p.parent_node_token), + await createNode( + client, + requireParam(p.space_id, "space_id"), + requireParam(p.title, "title"), + p.obj_type, + p.parent_node_token, + ), ); case "move": return jsonToolResult( await moveNode( client, - p.space_id, - p.node_token, + requireParam(p.space_id, "space_id"), + requireParam(p.node_token, "node_token"), p.target_space_id, p.target_parent_token, ), ); case "rename": - return jsonToolResult(await renameNode(client, p.space_id, p.node_token, p.title)); + return jsonToolResult( + await renameNode( + client, + requireParam(p.space_id, "space_id"), + requireParam(p.node_token, "node_token"), + requireParam(p.title, "title"), + ), + ); default: // eslint-disable-next-line @typescript-eslint/no-explicit-any -- exhaustive check fallback return unknownToolActionResult((p as { action?: unknown }).action);