@nextclaw/channel-plugin-feishu 0.2.17 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/channel-plugin-feishu",
3
- "version": "0.2.17",
3
+ "version": "0.2.18",
4
4
  "private": false,
5
5
  "description": "NextClaw Feishu/Lark channel plugin with doc/wiki/drive tools.",
6
6
  "type": "module",
@@ -1,4 +1,5 @@
1
- import { createToolContext, assertLarkOk, unixTimestampToISO8601 } from "./user-tool-helpers.js";
1
+ import type { createToolContext } from "./user-tool-helpers.js";
2
+ import { assertLarkOk, unixTimestampToISO8601 } from "./user-tool-helpers.js";
2
3
 
3
4
  export function normalizeEventTimes<T extends Record<string, unknown> | undefined>(event: T): T {
4
5
  if (!event) return event;
package/src/sheets.ts CHANGED
@@ -16,6 +16,26 @@ const MAX_READ_ROWS = 200;
16
16
  const MAX_WRITE_ROWS = 5000;
17
17
  const MAX_WRITE_COLS = 100;
18
18
 
19
+ type SheetParams = {
20
+ action: "info" | "read" | "write" | "append" | "find" | "create" | "export";
21
+ url?: string;
22
+ spreadsheet_token?: string;
23
+ range?: string;
24
+ sheet_id?: string;
25
+ value_render_option?: "FormattedValue" | "UnformattedValue" | "Formula" | "ToString";
26
+ values?: unknown[][];
27
+ find?: string;
28
+ match_case?: boolean;
29
+ match_entire_cell?: boolean;
30
+ search_by_regex?: boolean;
31
+ include_formulas?: boolean;
32
+ title?: string;
33
+ folder_token?: string;
34
+ headers?: unknown[];
35
+ data?: unknown[][];
36
+ file_extension?: "xlsx" | "csv";
37
+ };
38
+
19
39
  const SheetSchema = Type.Union([
20
40
  Type.Object({ action: Type.Literal("info"), url: Type.Optional(Type.String()), spreadsheet_token: Type.Optional(Type.String()) }),
21
41
  Type.Object({
@@ -74,25 +94,16 @@ function parseSheetUrl(url: string): { token: string; sheetId?: string } | null
74
94
  try {
75
95
  const parsed = new URL(url);
76
96
  const match = parsed.pathname.match(/\/sheets\/([^/?#]+)/);
77
- if (!match) {
78
- return null;
79
- }
80
- return {
81
- token: match[1],
82
- sheetId: parsed.searchParams.get("sheet") || undefined,
83
- };
97
+ if (!match) return null;
98
+ return { token: match[1], sheetId: parsed.searchParams.get("sheet") || undefined };
84
99
  } catch {
85
100
  return null;
86
101
  }
87
102
  }
88
103
 
89
- function resolveToken(params: { url?: string; spreadsheet_token?: string }) {
90
- if (params.spreadsheet_token) {
91
- return { token: params.spreadsheet_token };
92
- }
93
- if (params.url) {
94
- return parseSheetUrl(params.url);
95
- }
104
+ function resolveToken(params: SheetParams) {
105
+ if (params.spreadsheet_token) return { token: params.spreadsheet_token };
106
+ if (params.url) return parseSheetUrl(params.url);
96
107
  return null;
97
108
  }
98
109
 
@@ -108,9 +119,7 @@ function colLetter(n: number): string {
108
119
  }
109
120
 
110
121
  function flattenCellValue(cell: unknown): unknown {
111
- if (!Array.isArray(cell)) {
112
- return cell;
113
- }
122
+ if (!Array.isArray(cell)) return cell;
114
123
  if (cell.length > 0 && cell.every((segment) => segment && typeof segment === "object" && "text" in (segment as object))) {
115
124
  return cell.map((segment) => String((segment as { text?: unknown }).text ?? "")).join("");
116
125
  }
@@ -138,6 +147,256 @@ async function resolveSheetRange(
138
147
  return firstSheet.sheet_id;
139
148
  }
140
149
 
150
+ async function handleCreate(
151
+ client: ReturnType<typeof createToolContext>["toolClient"] extends () => infer T ? T : never,
152
+ payload: SheetParams,
153
+ ) {
154
+ const createResponse = await client.invoke(
155
+ "feishu_sheet.create",
156
+ (sdk, opts) =>
157
+ sdk.sheets.spreadsheet.create(
158
+ { data: { title: payload.title, folder_token: payload.folder_token } },
159
+ opts,
160
+ ),
161
+ { as: "user" },
162
+ );
163
+ assertLarkOk(createResponse);
164
+ const spreadsheet = (createResponse.data as { spreadsheet?: { spreadsheet_token?: string; title?: string } } | undefined)?.spreadsheet;
165
+ const token = spreadsheet?.spreadsheet_token;
166
+ if (!token) {
167
+ return json({ error: "创建电子表格失败,未返回 spreadsheet_token。" });
168
+ }
169
+ const allRows = [
170
+ ...(payload.headers ? [payload.headers] : []),
171
+ ...((payload.data as unknown[][] | undefined) ?? []),
172
+ ];
173
+ if (allRows.length > 0) {
174
+ const sheetsResponse = await client.invoke(
175
+ "feishu_sheet.create",
176
+ (sdk, opts) => sdk.sheets.spreadsheetSheet.query({ path: { spreadsheet_token: token } }, opts),
177
+ { as: "user" },
178
+ );
179
+ assertLarkOk(sheetsResponse);
180
+ const firstSheet = (sheetsResponse.data as { sheets?: Array<{ sheet_id?: string }> } | undefined)?.sheets?.[0];
181
+ if (firstSheet?.sheet_id) {
182
+ const numCols = Math.max(...allRows.map((row) => row.length), 1);
183
+ const range = `${firstSheet.sheet_id}!A1:${colLetter(numCols)}${allRows.length}`;
184
+ await client.invokeByPath("feishu_sheet.create", `/open-apis/sheets/v2/spreadsheets/${token}/values`, {
185
+ method: "PUT",
186
+ body: { valueRange: { range, values: allRows } },
187
+ as: "user",
188
+ });
189
+ }
190
+ }
191
+ return json({
192
+ spreadsheet_token: token,
193
+ title: spreadsheet?.title ?? payload.title,
194
+ url: `${wwwDomain(client.account.domain)}/sheets/${token}`,
195
+ });
196
+ }
197
+
198
+ async function handleInfo(
199
+ client: ReturnType<typeof createToolContext>["toolClient"] extends () => infer T ? T : never,
200
+ token: string,
201
+ ) {
202
+ const [spreadsheetRes, sheetsRes] = await Promise.all([
203
+ client.invoke(
204
+ "feishu_sheet.info",
205
+ (sdk, opts) => sdk.sheets.spreadsheet.get({ path: { spreadsheet_token: token } }, opts),
206
+ { as: "user" },
207
+ ),
208
+ client.invoke(
209
+ "feishu_sheet.info",
210
+ (sdk, opts) => sdk.sheets.spreadsheetSheet.query({ path: { spreadsheet_token: token } }, opts),
211
+ { as: "user" },
212
+ ),
213
+ ]);
214
+ assertLarkOk(spreadsheetRes);
215
+ assertLarkOk(sheetsRes);
216
+ return json({
217
+ title: (spreadsheetRes.data as { spreadsheet?: { title?: string } } | undefined)?.spreadsheet?.title,
218
+ spreadsheet_token: token,
219
+ url: `${wwwDomain(client.account.domain)}/sheets/${token}`,
220
+ sheets:
221
+ ((sheetsRes.data as { sheets?: Array<Record<string, unknown>> } | undefined)?.sheets ?? []).map((sheet) => ({
222
+ sheet_id: sheet.sheet_id,
223
+ title: sheet.title,
224
+ index: sheet.index,
225
+ row_count: (sheet.grid_properties as { row_count?: number } | undefined)?.row_count,
226
+ column_count: (sheet.grid_properties as { column_count?: number } | undefined)?.column_count,
227
+ })),
228
+ });
229
+ }
230
+
231
+ async function handleRead(
232
+ client: ReturnType<typeof createToolContext>["toolClient"] extends () => infer T ? T : never,
233
+ tokenInfo: { token: string; sheetId?: string },
234
+ payload: SheetParams,
235
+ ) {
236
+ const range = await resolveSheetRange(client, tokenInfo.token, payload.range, payload.sheet_id ?? tokenInfo.sheetId);
237
+ const response = await client.invokeByPath<{
238
+ data?: { valueRange?: { range?: string; values?: unknown[][] } };
239
+ }>("feishu_sheet.read", `/open-apis/sheets/v2/spreadsheets/${tokenInfo.token}/values/${encodeURIComponent(range)}`, {
240
+ method: "GET",
241
+ query: {
242
+ valueRenderOption: payload.value_render_option ?? "ToString",
243
+ dateTimeRenderOption: "FormattedString",
244
+ },
245
+ as: "user",
246
+ });
247
+ const values = (response.data?.valueRange?.values ?? []).map((row) => row.map(flattenCellValue));
248
+ return json({
249
+ range: response.data?.valueRange?.range,
250
+ values: values.slice(0, MAX_READ_ROWS),
251
+ ...(values.length > MAX_READ_ROWS
252
+ ? {
253
+ truncated: true,
254
+ total_rows: values.length,
255
+ hint: `结果超过 ${MAX_READ_ROWS} 行,已截断。请缩小 range 后重试。`,
256
+ }
257
+ : {}),
258
+ });
259
+ }
260
+
261
+ async function handleWrite(
262
+ client: ReturnType<typeof createToolContext>["toolClient"] extends () => infer T ? T : never,
263
+ tokenInfo: { token: string; sheetId?: string },
264
+ payload: SheetParams,
265
+ ) {
266
+ if ((payload.values?.length ?? 0) > MAX_WRITE_ROWS) {
267
+ return json({ error: `写入行数超过限制 ${MAX_WRITE_ROWS}` });
268
+ }
269
+ if ((payload.values ?? []).some((row) => row.length > MAX_WRITE_COLS)) {
270
+ return json({ error: `写入列数超过限制 ${MAX_WRITE_COLS}` });
271
+ }
272
+ const range = await resolveSheetRange(client, tokenInfo.token, payload.range, payload.sheet_id ?? tokenInfo.sheetId);
273
+ const response = await client.invokeByPath<{
274
+ data?: {
275
+ updatedRange?: string;
276
+ updatedRows?: number;
277
+ updatedColumns?: number;
278
+ updatedCells?: number;
279
+ revision?: number;
280
+ updates?: {
281
+ updatedRange?: string;
282
+ updatedRows?: number;
283
+ updatedColumns?: number;
284
+ updatedCells?: number;
285
+ revision?: number;
286
+ };
287
+ };
288
+ }>(
289
+ payload.action === "write" ? "feishu_sheet.write" : "feishu_sheet.append",
290
+ `/open-apis/sheets/v2/spreadsheets/${tokenInfo.token}/${payload.action === "write" ? "values" : "values_append"}`,
291
+ {
292
+ method: payload.action === "write" ? "PUT" : "POST",
293
+ body: { valueRange: { range, values: payload.values } },
294
+ as: "user",
295
+ },
296
+ );
297
+ const updates = response.data?.updates ?? response.data;
298
+ return json({
299
+ updated_range: updates?.updatedRange,
300
+ updated_rows: updates?.updatedRows,
301
+ updated_columns: updates?.updatedColumns,
302
+ updated_cells: updates?.updatedCells,
303
+ revision: updates?.revision,
304
+ });
305
+ }
306
+
307
+ async function handleFind(
308
+ client: ReturnType<typeof createToolContext>["toolClient"] extends () => infer T ? T : never,
309
+ token: string,
310
+ payload: SheetParams,
311
+ ) {
312
+ const response = await client.invoke(
313
+ "feishu_sheet.find",
314
+ (sdk, opts) =>
315
+ sdk.sheets.spreadsheetSheet.find(
316
+ {
317
+ path: { spreadsheet_token: token, sheet_id: payload.sheet_id! },
318
+ data: {
319
+ find: payload.find,
320
+ find_condition: {
321
+ range: payload.range ? `${payload.sheet_id}!${payload.range}` : payload.sheet_id,
322
+ ...(payload.match_case !== undefined ? { match_case: !payload.match_case } : {}),
323
+ ...(payload.match_entire_cell !== undefined ? { match_entire_cell: payload.match_entire_cell } : {}),
324
+ ...(payload.search_by_regex !== undefined ? { search_by_regex: payload.search_by_regex } : {}),
325
+ ...(payload.include_formulas !== undefined ? { include_formulas: payload.include_formulas } : {}),
326
+ },
327
+ },
328
+ },
329
+ opts,
330
+ ),
331
+ { as: "user" },
332
+ );
333
+ assertLarkOk(response);
334
+ return json({
335
+ matched_cells: (response.data as { find_result?: { matched_cells?: unknown[] } } | undefined)?.find_result?.matched_cells ?? [],
336
+ matched_formula_cells:
337
+ (response.data as { find_result?: { matched_formula_cells?: unknown[] } } | undefined)?.find_result?.matched_formula_cells ?? [],
338
+ });
339
+ }
340
+
341
+ async function handleExport(
342
+ client: ReturnType<typeof createToolContext>["toolClient"] extends () => infer T ? T : never,
343
+ token: string,
344
+ payload: SheetParams,
345
+ ) {
346
+ const exportCreate = await client.invoke(
347
+ "feishu_sheet.export",
348
+ (sdk, opts) =>
349
+ sdk.drive.exportTask.create(
350
+ {
351
+ data: {
352
+ file_extension: payload.file_extension,
353
+ token,
354
+ type: "sheet",
355
+ sub_id: payload.sheet_id,
356
+ },
357
+ },
358
+ opts,
359
+ ),
360
+ { as: "user" },
361
+ );
362
+ assertLarkOk(exportCreate);
363
+ const ticket = (exportCreate.data as { ticket?: string } | undefined)?.ticket;
364
+ if (!ticket) {
365
+ return json({ error: "导出任务创建失败,未返回 ticket。" });
366
+ }
367
+ for (let i = 0; i < 30; i += 1) {
368
+ await new Promise((resolve) => setTimeout(resolve, 1000));
369
+ const exportStatus = await client.invoke(
370
+ "feishu_sheet.export",
371
+ (sdk, opts) =>
372
+ sdk.drive.exportTask.get({ path: { ticket }, params: { token } }, opts),
373
+ { as: "user" },
374
+ );
375
+ assertLarkOk(exportStatus);
376
+ const result = (exportStatus.data as {
377
+ result?: {
378
+ job_status?: number;
379
+ file_token?: string;
380
+ file_name?: string;
381
+ file_size?: number;
382
+ job_error_msg?: string;
383
+ };
384
+ } | undefined)?.result;
385
+ if (result?.job_status === 0) {
386
+ return json({
387
+ file_token: result.file_token,
388
+ file_name: result.file_name,
389
+ file_size: result.file_size,
390
+ file_extension: payload.file_extension,
391
+ });
392
+ }
393
+ if ((result?.job_status ?? 0) >= 3) {
394
+ return json({ error: result?.job_error_msg ?? `导出失败 (status=${result?.job_status})` });
395
+ }
396
+ }
397
+ return json({ error: "导出任务超时,请稍后重试。" });
398
+ }
399
+
141
400
  export function registerFeishuSheetsTools(api: OpenClawPluginApi) {
142
401
  if (!api.config) return;
143
402
  const accounts = listEnabledFeishuAccounts(api.config);
@@ -150,222 +409,20 @@ export function registerFeishuSheetsTools(api: OpenClawPluginApi) {
150
409
  description: "按本人身份读取、写入、创建、查找和导出飞书电子表格。",
151
410
  parameters: SheetSchema,
152
411
  async execute(_toolCallId, params) {
153
- const payload = params as any;
412
+ const payload = params as SheetParams;
154
413
  try {
155
414
  const client = createToolContext(api, "feishu_sheet").toolClient();
156
-
157
- if (payload.action === "create") {
158
- const createResponse = await client.invoke(
159
- "feishu_sheet.create",
160
- (sdk, opts) =>
161
- sdk.sheets.spreadsheet.create(
162
- {
163
- data: {
164
- title: payload.title,
165
- folder_token: payload.folder_token,
166
- },
167
- },
168
- opts,
169
- ),
170
- { as: "user" },
171
- );
172
- assertLarkOk(createResponse);
173
- const spreadsheet = (createResponse.data as { spreadsheet?: { spreadsheet_token?: string; title?: string } } | undefined)?.spreadsheet;
174
- const token = spreadsheet?.spreadsheet_token;
175
- if (!token) {
176
- return json({ error: "创建电子表格失败,未返回 spreadsheet_token。" });
177
- }
178
- if (payload.headers || payload.data) {
179
- const sheetsResponse = await client.invoke(
180
- "feishu_sheet.create",
181
- (sdk, opts) => sdk.sheets.spreadsheetSheet.query({ path: { spreadsheet_token: token } }, opts),
182
- { as: "user" },
183
- );
184
- assertLarkOk(sheetsResponse);
185
- const firstSheet = (sheetsResponse.data as { sheets?: Array<{ sheet_id?: string }> } | undefined)?.sheets?.[0];
186
- const allRows = [
187
- ...(payload.headers ? [payload.headers] : []),
188
- ...((payload.data as unknown[][] | undefined) ?? []),
189
- ];
190
- if (firstSheet?.sheet_id && allRows.length > 0) {
191
- const numCols = Math.max(...allRows.map((row) => row.length), 1);
192
- const range = `${firstSheet.sheet_id}!A1:${colLetter(numCols)}${allRows.length}`;
193
- await client.invokeByPath("feishu_sheet.create", `/open-apis/sheets/v2/spreadsheets/${token}/values`, {
194
- method: "PUT",
195
- body: { valueRange: { range, values: allRows } },
196
- as: "user",
197
- });
198
- }
199
- }
200
- return json({
201
- spreadsheet_token: token,
202
- title: spreadsheet?.title ?? payload.title,
203
- url: `${wwwDomain(client.account.domain)}/sheets/${token}`,
204
- });
205
- }
415
+ if (payload.action === "create") return handleCreate(client, payload);
206
416
 
207
417
  const tokenInfo = resolveToken(payload);
208
418
  if (!tokenInfo?.token) {
209
419
  return json({ error: "请传 spreadsheet_token 或 /sheets/TOKEN 形式的 url。" });
210
420
  }
211
-
212
- if (payload.action === "info") {
213
- const [spreadsheetRes, sheetsRes] = await Promise.all([
214
- client.invoke(
215
- "feishu_sheet.info",
216
- (sdk, opts) => sdk.sheets.spreadsheet.get({ path: { spreadsheet_token: tokenInfo.token } }, opts),
217
- { as: "user" },
218
- ),
219
- client.invoke(
220
- "feishu_sheet.info",
221
- (sdk, opts) => sdk.sheets.spreadsheetSheet.query({ path: { spreadsheet_token: tokenInfo.token } }, opts),
222
- { as: "user" },
223
- ),
224
- ]);
225
- assertLarkOk(spreadsheetRes);
226
- assertLarkOk(sheetsRes);
227
- return json({
228
- title: (spreadsheetRes.data as { spreadsheet?: { title?: string } } | undefined)?.spreadsheet?.title,
229
- spreadsheet_token: tokenInfo.token,
230
- url: `${wwwDomain(client.account.domain)}/sheets/${tokenInfo.token}`,
231
- sheets:
232
- ((sheetsRes.data as { sheets?: Array<Record<string, unknown>> } | undefined)?.sheets ?? []).map((sheet) => ({
233
- sheet_id: sheet.sheet_id,
234
- title: sheet.title,
235
- index: sheet.index,
236
- row_count: (sheet.grid_properties as { row_count?: number } | undefined)?.row_count,
237
- column_count: (sheet.grid_properties as { column_count?: number } | undefined)?.column_count,
238
- })),
239
- });
240
- }
241
-
242
- if (payload.action === "read") {
243
- const range = await resolveSheetRange(client, tokenInfo.token, payload.range, payload.sheet_id ?? tokenInfo.sheetId);
244
- const response = await client.invokeByPath<{
245
- data?: { valueRange?: { range?: string; values?: unknown[][] } };
246
- }>("feishu_sheet.read", `/open-apis/sheets/v2/spreadsheets/${tokenInfo.token}/values/${encodeURIComponent(range)}`, {
247
- method: "GET",
248
- query: {
249
- valueRenderOption: payload.value_render_option ?? "ToString",
250
- dateTimeRenderOption: "FormattedString",
251
- },
252
- as: "user",
253
- });
254
- const values = (response.data?.valueRange?.values ?? []).map((row) => row.map(flattenCellValue));
255
- return json({
256
- range: response.data?.valueRange?.range,
257
- values: values.slice(0, MAX_READ_ROWS),
258
- ...(values.length > MAX_READ_ROWS
259
- ? {
260
- truncated: true,
261
- total_rows: values.length,
262
- hint: `结果超过 ${MAX_READ_ROWS} 行,已截断。请缩小 range 后重试。`,
263
- }
264
- : {}),
265
- });
266
- }
267
-
268
- if (payload.action === "write" || payload.action === "append") {
269
- if (payload.values.length > MAX_WRITE_ROWS) {
270
- return json({ error: `写入行数超过限制 ${MAX_WRITE_ROWS}` });
271
- }
272
- if (payload.values.some((row: unknown[]) => row.length > MAX_WRITE_COLS)) {
273
- return json({ error: `写入列数超过限制 ${MAX_WRITE_COLS}` });
274
- }
275
- const range = await resolveSheetRange(client, tokenInfo.token, payload.range, payload.sheet_id ?? tokenInfo.sheetId);
276
- const response = await client.invokeByPath<any>(
277
- payload.action === "write" ? "feishu_sheet.write" : "feishu_sheet.append",
278
- `/open-apis/sheets/v2/spreadsheets/${tokenInfo.token}/${payload.action === "write" ? "values" : "values_append"}`,
279
- {
280
- method: payload.action === "write" ? "PUT" : "POST",
281
- body: { valueRange: { range, values: payload.values } },
282
- as: "user",
283
- },
284
- );
285
- const updates = response.data?.updates ?? response.data;
286
- return json({
287
- updated_range: updates?.updatedRange,
288
- updated_rows: updates?.updatedRows,
289
- updated_columns: updates?.updatedColumns,
290
- updated_cells: updates?.updatedCells,
291
- revision: updates?.revision,
292
- });
293
- }
294
-
295
- if (payload.action === "find") {
296
- const response = await client.invoke(
297
- "feishu_sheet.find",
298
- (sdk, opts) =>
299
- sdk.sheets.spreadsheetSheet.find(
300
- {
301
- path: { spreadsheet_token: tokenInfo.token, sheet_id: payload.sheet_id },
302
- data: {
303
- find: payload.find,
304
- find_condition: {
305
- range: payload.range ? `${payload.sheet_id}!${payload.range}` : payload.sheet_id,
306
- ...(payload.match_case !== undefined ? { match_case: !payload.match_case } : {}),
307
- ...(payload.match_entire_cell !== undefined ? { match_entire_cell: payload.match_entire_cell } : {}),
308
- ...(payload.search_by_regex !== undefined ? { search_by_regex: payload.search_by_regex } : {}),
309
- ...(payload.include_formulas !== undefined ? { include_formulas: payload.include_formulas } : {}),
310
- },
311
- },
312
- },
313
- opts,
314
- ),
315
- { as: "user" },
316
- );
317
- assertLarkOk(response);
318
- return json({
319
- matched_cells: (response.data as { find_result?: { matched_cells?: unknown[] } } | undefined)?.find_result?.matched_cells ?? [],
320
- matched_formula_cells:
321
- (response.data as { find_result?: { matched_formula_cells?: unknown[] } } | undefined)?.find_result?.matched_formula_cells ?? [],
322
- });
323
- }
324
-
325
- const exportCreate = await client.invoke(
326
- "feishu_sheet.export",
327
- (sdk, opts) =>
328
- sdk.drive.exportTask.create(
329
- {
330
- data: {
331
- file_extension: payload.file_extension,
332
- token: tokenInfo.token,
333
- type: "sheet",
334
- sub_id: payload.sheet_id,
335
- },
336
- },
337
- opts,
338
- ),
339
- { as: "user" },
340
- );
341
- assertLarkOk(exportCreate);
342
- const ticket = (exportCreate.data as { ticket?: string } | undefined)?.ticket;
343
- if (!ticket) {
344
- return json({ error: "导出任务创建失败,未返回 ticket。" });
345
- }
346
- for (let i = 0; i < 30; i += 1) {
347
- await new Promise((resolve) => setTimeout(resolve, 1000));
348
- const exportStatus = await client.invoke(
349
- "feishu_sheet.export",
350
- (sdk, opts) =>
351
- sdk.drive.exportTask.get({ path: { ticket }, params: { token: tokenInfo.token } }, opts),
352
- { as: "user" },
353
- );
354
- assertLarkOk(exportStatus);
355
- const result = (exportStatus.data as { result?: { job_status?: number; file_token?: string; file_name?: string; file_size?: number; job_error_msg?: string } } | undefined)?.result;
356
- if (result?.job_status === 0) {
357
- return json({
358
- file_token: result.file_token,
359
- file_name: result.file_name,
360
- file_size: result.file_size,
361
- file_extension: payload.file_extension,
362
- });
363
- }
364
- if ((result?.job_status ?? 0) >= 3) {
365
- return json({ error: result?.job_error_msg ?? `导出失败 (status=${result?.job_status})` });
366
- }
367
- }
368
- return json({ error: "导出任务超时,请稍后重试。" });
421
+ if (payload.action === "info") return handleInfo(client, tokenInfo.token);
422
+ if (payload.action === "read") return handleRead(client, tokenInfo, payload);
423
+ if (payload.action === "write" || payload.action === "append") return handleWrite(client, tokenInfo, payload);
424
+ if (payload.action === "find") return handleFind(client, tokenInfo.token, payload);
425
+ return handleExport(client, tokenInfo.token, payload);
369
426
  } catch (error) {
370
427
  return handleInvokeError(error, api);
371
428
  }
@@ -3,12 +3,32 @@ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
3
3
  import { assertLarkOk, createToolContext, handleInvokeError, json, registerTool, StringEnum } from "./user-tool-helpers.js";
4
4
 
5
5
  const TasklistSchema = Type.Union([
6
- Type.Object({ action: Type.Literal("create"), name: Type.String(), members: Type.Optional(Type.Array(Type.Object({ id: Type.String(), role: Type.Optional(StringEnum(["editor", "viewer"])) }))) }),
6
+ Type.Object({
7
+ action: Type.Literal("create"),
8
+ name: Type.String(),
9
+ members: Type.Optional(
10
+ Type.Array(
11
+ Type.Object({
12
+ id: Type.String(),
13
+ role: Type.Optional(StringEnum(["editor", "viewer"])),
14
+ }),
15
+ ),
16
+ ),
17
+ }),
7
18
  Type.Object({ action: Type.Literal("get"), tasklist_guid: Type.String() }),
8
19
  Type.Object({ action: Type.Literal("list"), page_size: Type.Optional(Type.Number()), page_token: Type.Optional(Type.String()) }),
9
20
  Type.Object({ action: Type.Literal("tasks"), tasklist_guid: Type.String(), page_size: Type.Optional(Type.Number()), page_token: Type.Optional(Type.String()), completed: Type.Optional(Type.Boolean()) }),
10
21
  Type.Object({ action: Type.Literal("patch"), tasklist_guid: Type.String(), name: Type.Optional(Type.String()) }),
11
- Type.Object({ action: Type.Literal("add_members"), tasklist_guid: Type.String(), members: Type.Array(Type.Object({ id: Type.String(), role: Type.Optional(StringEnum(["editor", "viewer"])) }))) }),
22
+ Type.Object({
23
+ action: Type.Literal("add_members"),
24
+ tasklist_guid: Type.String(),
25
+ members: Type.Array(
26
+ Type.Object({
27
+ id: Type.String(),
28
+ role: Type.Optional(StringEnum(["editor", "viewer"])),
29
+ }),
30
+ ),
31
+ }),
12
32
  ]);
13
33
 
14
34
  export function registerFeishuTaskTasklistTool(api: OpenClawPluginApi) {