@openclaw/feishu 2026.2.14 → 2026.2.17
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 +1 -1
- package/src/bitable.ts +281 -3
- package/src/bot.checkBotMentioned.test.ts +64 -0
- package/src/bot.test.ts +28 -4
- package/src/bot.ts +22 -29
- package/src/channel.ts +9 -15
- package/src/docx.ts +2 -2
- package/src/dynamic-agent.ts +1 -1
- package/src/media.ts +10 -37
- package/src/monitor.ts +2 -2
- package/src/onboarding.ts +1 -1
- package/src/policy.ts +8 -28
- package/src/probe.ts +1 -1
- package/src/reply-dispatcher.ts +1 -1
- package/src/send-result.ts +29 -0
- package/src/send.ts +11 -38
- package/src/types.ts +2 -3
package/package.json
CHANGED
package/src/bitable.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
1
|
import { Type } from "@sinclair/typebox";
|
|
3
|
-
import type {
|
|
2
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
4
3
|
import { createFeishuClient } from "./client.js";
|
|
4
|
+
import type { FeishuConfig } from "./types.js";
|
|
5
5
|
|
|
6
6
|
// ============ Helpers ============
|
|
7
7
|
|
|
@@ -224,6 +224,198 @@ async function createRecord(
|
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
/** Logger interface for cleanup operations */
|
|
228
|
+
type CleanupLogger = {
|
|
229
|
+
debug: (msg: string) => void;
|
|
230
|
+
warn: (msg: string) => void;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/** Default field types created for new Bitable tables (to be cleaned up) */
|
|
234
|
+
const DEFAULT_CLEANUP_FIELD_TYPES = new Set([3, 5, 17]); // SingleSelect, DateTime, Attachment
|
|
235
|
+
|
|
236
|
+
/** Clean up default placeholder rows and fields in a newly created Bitable table */
|
|
237
|
+
async function cleanupNewBitable(
|
|
238
|
+
client: ReturnType<typeof createFeishuClient>,
|
|
239
|
+
appToken: string,
|
|
240
|
+
tableId: string,
|
|
241
|
+
tableName: string,
|
|
242
|
+
logger: CleanupLogger,
|
|
243
|
+
): Promise<{ cleanedRows: number; cleanedFields: number }> {
|
|
244
|
+
let cleanedRows = 0;
|
|
245
|
+
let cleanedFields = 0;
|
|
246
|
+
|
|
247
|
+
// Step 1: Clean up default fields
|
|
248
|
+
const fieldsRes = await client.bitable.appTableField.list({
|
|
249
|
+
path: { app_token: appToken, table_id: tableId },
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (fieldsRes.code === 0 && fieldsRes.data?.items) {
|
|
253
|
+
// Step 1a: Rename primary field to the table name (works for both Feishu and Lark)
|
|
254
|
+
const primaryField = fieldsRes.data.items.find((f) => f.is_primary);
|
|
255
|
+
if (primaryField?.field_id) {
|
|
256
|
+
try {
|
|
257
|
+
const newFieldName = tableName.length <= 20 ? tableName : "Name";
|
|
258
|
+
await client.bitable.appTableField.update({
|
|
259
|
+
path: {
|
|
260
|
+
app_token: appToken,
|
|
261
|
+
table_id: tableId,
|
|
262
|
+
field_id: primaryField.field_id,
|
|
263
|
+
},
|
|
264
|
+
data: {
|
|
265
|
+
field_name: newFieldName,
|
|
266
|
+
type: 1,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
cleanedFields++;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
logger.debug(`Failed to rename primary field: ${err}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Step 1b: Delete default placeholder fields by type (works for both Feishu and Lark)
|
|
276
|
+
const defaultFieldsToDelete = fieldsRes.data.items.filter(
|
|
277
|
+
(f) => !f.is_primary && DEFAULT_CLEANUP_FIELD_TYPES.has(f.type ?? 0),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
for (const field of defaultFieldsToDelete) {
|
|
281
|
+
if (field.field_id) {
|
|
282
|
+
try {
|
|
283
|
+
await client.bitable.appTableField.delete({
|
|
284
|
+
path: {
|
|
285
|
+
app_token: appToken,
|
|
286
|
+
table_id: tableId,
|
|
287
|
+
field_id: field.field_id,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
cleanedFields++;
|
|
291
|
+
} catch (err) {
|
|
292
|
+
logger.debug(`Failed to delete default field ${field.field_name}: ${err}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Step 2: Delete empty placeholder rows (batch when possible)
|
|
299
|
+
const recordsRes = await client.bitable.appTableRecord.list({
|
|
300
|
+
path: { app_token: appToken, table_id: tableId },
|
|
301
|
+
params: { page_size: 100 },
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (recordsRes.code === 0 && recordsRes.data?.items) {
|
|
305
|
+
const emptyRecordIds = recordsRes.data.items
|
|
306
|
+
.filter((r) => !r.fields || Object.keys(r.fields).length === 0)
|
|
307
|
+
.map((r) => r.record_id)
|
|
308
|
+
.filter((id): id is string => Boolean(id));
|
|
309
|
+
|
|
310
|
+
if (emptyRecordIds.length > 0) {
|
|
311
|
+
try {
|
|
312
|
+
await client.bitable.appTableRecord.batchDelete({
|
|
313
|
+
path: { app_token: appToken, table_id: tableId },
|
|
314
|
+
data: { records: emptyRecordIds },
|
|
315
|
+
});
|
|
316
|
+
cleanedRows = emptyRecordIds.length;
|
|
317
|
+
} catch {
|
|
318
|
+
// Fallback: delete one by one if batch API is unavailable
|
|
319
|
+
for (const recordId of emptyRecordIds) {
|
|
320
|
+
try {
|
|
321
|
+
await client.bitable.appTableRecord.delete({
|
|
322
|
+
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
|
323
|
+
});
|
|
324
|
+
cleanedRows++;
|
|
325
|
+
} catch (err) {
|
|
326
|
+
logger.debug(`Failed to delete empty row ${recordId}: ${err}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return { cleanedRows, cleanedFields };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function createApp(
|
|
337
|
+
client: ReturnType<typeof createFeishuClient>,
|
|
338
|
+
name: string,
|
|
339
|
+
folderToken?: string,
|
|
340
|
+
logger?: CleanupLogger,
|
|
341
|
+
) {
|
|
342
|
+
const res = await client.bitable.app.create({
|
|
343
|
+
data: {
|
|
344
|
+
name,
|
|
345
|
+
...(folderToken && { folder_token: folderToken }),
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
if (res.code !== 0) {
|
|
349
|
+
throw new Error(res.msg);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const appToken = res.data?.app?.app_token;
|
|
353
|
+
if (!appToken) {
|
|
354
|
+
throw new Error("Failed to create Bitable: no app_token returned");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const log: CleanupLogger = logger ?? { debug: () => {}, warn: () => {} };
|
|
358
|
+
let tableId: string | undefined;
|
|
359
|
+
let cleanedRows = 0;
|
|
360
|
+
let cleanedFields = 0;
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const tablesRes = await client.bitable.appTable.list({
|
|
364
|
+
path: { app_token: appToken },
|
|
365
|
+
});
|
|
366
|
+
if (tablesRes.code === 0 && tablesRes.data?.items && tablesRes.data.items.length > 0) {
|
|
367
|
+
tableId = tablesRes.data.items[0].table_id ?? undefined;
|
|
368
|
+
if (tableId) {
|
|
369
|
+
const cleanup = await cleanupNewBitable(client, appToken, tableId, name, log);
|
|
370
|
+
cleanedRows = cleanup.cleanedRows;
|
|
371
|
+
cleanedFields = cleanup.cleanedFields;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
} catch (err) {
|
|
375
|
+
log.debug(`Cleanup failed (non-critical): ${err}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
app_token: appToken,
|
|
380
|
+
table_id: tableId,
|
|
381
|
+
name: res.data?.app?.name,
|
|
382
|
+
url: res.data?.app?.url,
|
|
383
|
+
cleaned_placeholder_rows: cleanedRows,
|
|
384
|
+
cleaned_default_fields: cleanedFields,
|
|
385
|
+
hint: tableId
|
|
386
|
+
? `Table created. Use app_token="${appToken}" and table_id="${tableId}" for other bitable tools.`
|
|
387
|
+
: "Table created. Use feishu_bitable_get_meta to get table_id and field details.",
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function createField(
|
|
392
|
+
client: ReturnType<typeof createFeishuClient>,
|
|
393
|
+
appToken: string,
|
|
394
|
+
tableId: string,
|
|
395
|
+
fieldName: string,
|
|
396
|
+
fieldType: number,
|
|
397
|
+
property?: Record<string, unknown>,
|
|
398
|
+
) {
|
|
399
|
+
const res = await client.bitable.appTableField.create({
|
|
400
|
+
path: { app_token: appToken, table_id: tableId },
|
|
401
|
+
data: {
|
|
402
|
+
field_name: fieldName,
|
|
403
|
+
type: fieldType,
|
|
404
|
+
...(property && { property }),
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
if (res.code !== 0) {
|
|
408
|
+
throw new Error(res.msg);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
field_id: res.data?.field?.field_id,
|
|
413
|
+
field_name: res.data?.field?.field_name,
|
|
414
|
+
type: res.data?.field?.type,
|
|
415
|
+
type_name: FIELD_TYPE_NAMES[res.data?.field?.type ?? 0] || `type_${res.data?.field?.type}`,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
227
419
|
async function updateRecord(
|
|
228
420
|
client: ReturnType<typeof createFeishuClient>,
|
|
229
421
|
appToken: string,
|
|
@@ -296,6 +488,36 @@ const CreateRecordSchema = Type.Object({
|
|
|
296
488
|
}),
|
|
297
489
|
});
|
|
298
490
|
|
|
491
|
+
const CreateAppSchema = Type.Object({
|
|
492
|
+
name: Type.String({
|
|
493
|
+
description: "Name for the new Bitable application",
|
|
494
|
+
}),
|
|
495
|
+
folder_token: Type.Optional(
|
|
496
|
+
Type.String({
|
|
497
|
+
description: "Optional folder token to place the Bitable in a specific folder",
|
|
498
|
+
}),
|
|
499
|
+
),
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const CreateFieldSchema = Type.Object({
|
|
503
|
+
app_token: Type.String({
|
|
504
|
+
description:
|
|
505
|
+
"Bitable app token (use feishu_bitable_get_meta to get from URL, or feishu_bitable_create_app to create new)",
|
|
506
|
+
}),
|
|
507
|
+
table_id: Type.String({ description: "Table ID (from URL: ?table=YYY)" }),
|
|
508
|
+
field_name: Type.String({ description: "Name for the new field" }),
|
|
509
|
+
field_type: Type.Number({
|
|
510
|
+
description:
|
|
511
|
+
"Field type ID: 1=Text, 2=Number, 3=SingleSelect, 4=MultiSelect, 5=DateTime, 7=Checkbox, 11=User, 13=Phone, 15=URL, 17=Attachment, 18=SingleLink, 19=Lookup, 20=Formula, 21=DuplexLink, 22=Location, 23=GroupChat, 1001=CreatedTime, 1002=ModifiedTime, 1003=CreatedUser, 1004=ModifiedUser, 1005=AutoNumber",
|
|
512
|
+
minimum: 1,
|
|
513
|
+
}),
|
|
514
|
+
property: Type.Optional(
|
|
515
|
+
Type.Record(Type.String(), Type.Any(), {
|
|
516
|
+
description: "Field-specific properties (e.g., options for SingleSelect, format for Number)",
|
|
517
|
+
}),
|
|
518
|
+
),
|
|
519
|
+
});
|
|
520
|
+
|
|
299
521
|
const UpdateRecordSchema = Type.Object({
|
|
300
522
|
app_token: Type.String({
|
|
301
523
|
description: "Bitable app token (use feishu_bitable_get_meta to get from URL)",
|
|
@@ -457,5 +679,61 @@ export function registerFeishuBitableTools(api: OpenClawPluginApi) {
|
|
|
457
679
|
{ name: "feishu_bitable_update_record" },
|
|
458
680
|
);
|
|
459
681
|
|
|
460
|
-
|
|
682
|
+
// Tool 6: feishu_bitable_create_app
|
|
683
|
+
api.registerTool(
|
|
684
|
+
{
|
|
685
|
+
name: "feishu_bitable_create_app",
|
|
686
|
+
label: "Feishu Bitable Create App",
|
|
687
|
+
description: "Create a new Bitable (multidimensional table) application",
|
|
688
|
+
parameters: CreateAppSchema,
|
|
689
|
+
async execute(_toolCallId, params) {
|
|
690
|
+
const { name, folder_token } = params as { name: string; folder_token?: string };
|
|
691
|
+
try {
|
|
692
|
+
const result = await createApp(getClient(), name, folder_token, {
|
|
693
|
+
debug: (msg) => api.logger.debug?.(msg),
|
|
694
|
+
warn: (msg) => api.logger.warn?.(msg),
|
|
695
|
+
});
|
|
696
|
+
return json(result);
|
|
697
|
+
} catch (err) {
|
|
698
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
{ name: "feishu_bitable_create_app" },
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
// Tool 7: feishu_bitable_create_field
|
|
706
|
+
api.registerTool(
|
|
707
|
+
{
|
|
708
|
+
name: "feishu_bitable_create_field",
|
|
709
|
+
label: "Feishu Bitable Create Field",
|
|
710
|
+
description: "Create a new field (column) in a Bitable table",
|
|
711
|
+
parameters: CreateFieldSchema,
|
|
712
|
+
async execute(_toolCallId, params) {
|
|
713
|
+
const { app_token, table_id, field_name, field_type, property } = params as {
|
|
714
|
+
app_token: string;
|
|
715
|
+
table_id: string;
|
|
716
|
+
field_name: string;
|
|
717
|
+
field_type: number;
|
|
718
|
+
property?: Record<string, unknown>;
|
|
719
|
+
};
|
|
720
|
+
try {
|
|
721
|
+
const result = await createField(
|
|
722
|
+
getClient(),
|
|
723
|
+
app_token,
|
|
724
|
+
table_id,
|
|
725
|
+
field_name,
|
|
726
|
+
field_type,
|
|
727
|
+
property,
|
|
728
|
+
);
|
|
729
|
+
return json(result);
|
|
730
|
+
} catch (err) {
|
|
731
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
},
|
|
735
|
+
{ name: "feishu_bitable_create_field" },
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
api.logger.info?.("feishu_bitable: Registered bitable tools");
|
|
461
739
|
}
|
|
@@ -61,4 +61,68 @@ describe("parseFeishuMessageEvent – mentionedBot", () => {
|
|
|
61
61
|
const ctx = parseFeishuMessageEvent(event as any, "");
|
|
62
62
|
expect(ctx.mentionedBot).toBe(false);
|
|
63
63
|
});
|
|
64
|
+
|
|
65
|
+
it("returns mentionedBot=true for post message with at (no top-level mentions)", () => {
|
|
66
|
+
const BOT_OPEN_ID = "ou_bot_123";
|
|
67
|
+
const postContent = JSON.stringify({
|
|
68
|
+
content: [
|
|
69
|
+
[{ tag: "at", user_id: BOT_OPEN_ID, user_name: "claw" }],
|
|
70
|
+
[{ tag: "text", text: "What does this document say" }],
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
const event = {
|
|
74
|
+
sender: { sender_id: { user_id: "u1", open_id: "ou_sender" } },
|
|
75
|
+
message: {
|
|
76
|
+
message_id: "msg_1",
|
|
77
|
+
chat_id: "oc_chat1",
|
|
78
|
+
chat_type: "group",
|
|
79
|
+
message_type: "post",
|
|
80
|
+
content: postContent,
|
|
81
|
+
mentions: [],
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
const ctx = parseFeishuMessageEvent(event as any, BOT_OPEN_ID);
|
|
85
|
+
expect(ctx.mentionedBot).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns mentionedBot=false for post message with no at", () => {
|
|
89
|
+
const postContent = JSON.stringify({
|
|
90
|
+
content: [[{ tag: "text", text: "hello" }]],
|
|
91
|
+
});
|
|
92
|
+
const event = {
|
|
93
|
+
sender: { sender_id: { user_id: "u1", open_id: "ou_sender" } },
|
|
94
|
+
message: {
|
|
95
|
+
message_id: "msg_1",
|
|
96
|
+
chat_id: "oc_chat1",
|
|
97
|
+
chat_type: "group",
|
|
98
|
+
message_type: "post",
|
|
99
|
+
content: postContent,
|
|
100
|
+
mentions: [],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const ctx = parseFeishuMessageEvent(event as any, "ou_bot_123");
|
|
104
|
+
expect(ctx.mentionedBot).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("returns mentionedBot=false for post message with at for another user", () => {
|
|
108
|
+
const postContent = JSON.stringify({
|
|
109
|
+
content: [
|
|
110
|
+
[{ tag: "at", user_id: "ou_other", user_name: "other" }],
|
|
111
|
+
[{ tag: "text", text: "hello" }],
|
|
112
|
+
],
|
|
113
|
+
});
|
|
114
|
+
const event = {
|
|
115
|
+
sender: { sender_id: { user_id: "u1", open_id: "ou_sender" } },
|
|
116
|
+
message: {
|
|
117
|
+
message_id: "msg_1",
|
|
118
|
+
chat_id: "oc_chat1",
|
|
119
|
+
chat_type: "group",
|
|
120
|
+
message_type: "post",
|
|
121
|
+
content: postContent,
|
|
122
|
+
mentions: [],
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
const ctx = parseFeishuMessageEvent(event as any, "ou_bot_123");
|
|
126
|
+
expect(ctx.mentionedBot).toBe(false);
|
|
127
|
+
});
|
|
64
128
|
});
|
package/src/bot.test.ts
CHANGED
|
@@ -99,7 +99,13 @@ describe("handleFeishuMessage command authorization", () => {
|
|
|
99
99
|
await handleFeishuMessage({
|
|
100
100
|
cfg,
|
|
101
101
|
event,
|
|
102
|
-
runtime: {
|
|
102
|
+
runtime: {
|
|
103
|
+
log: vi.fn(),
|
|
104
|
+
error: vi.fn(),
|
|
105
|
+
exit: vi.fn((code: number): never => {
|
|
106
|
+
throw new Error(`exit ${code}`);
|
|
107
|
+
}),
|
|
108
|
+
} as RuntimeEnv,
|
|
103
109
|
});
|
|
104
110
|
|
|
105
111
|
expect(mockResolveCommandAuthorizedFromAuthorizers).toHaveBeenCalledWith({
|
|
@@ -148,7 +154,13 @@ describe("handleFeishuMessage command authorization", () => {
|
|
|
148
154
|
await handleFeishuMessage({
|
|
149
155
|
cfg,
|
|
150
156
|
event,
|
|
151
|
-
runtime: {
|
|
157
|
+
runtime: {
|
|
158
|
+
log: vi.fn(),
|
|
159
|
+
error: vi.fn(),
|
|
160
|
+
exit: vi.fn((code: number): never => {
|
|
161
|
+
throw new Error(`exit ${code}`);
|
|
162
|
+
}),
|
|
163
|
+
} as RuntimeEnv,
|
|
152
164
|
});
|
|
153
165
|
|
|
154
166
|
expect(mockReadAllowFromStore).toHaveBeenCalledWith("feishu");
|
|
@@ -189,7 +201,13 @@ describe("handleFeishuMessage command authorization", () => {
|
|
|
189
201
|
await handleFeishuMessage({
|
|
190
202
|
cfg,
|
|
191
203
|
event,
|
|
192
|
-
runtime: {
|
|
204
|
+
runtime: {
|
|
205
|
+
log: vi.fn(),
|
|
206
|
+
error: vi.fn(),
|
|
207
|
+
exit: vi.fn((code: number): never => {
|
|
208
|
+
throw new Error(`exit ${code}`);
|
|
209
|
+
}),
|
|
210
|
+
} as RuntimeEnv,
|
|
193
211
|
});
|
|
194
212
|
|
|
195
213
|
expect(mockUpsertPairingRequest).toHaveBeenCalledWith({
|
|
@@ -247,7 +265,13 @@ describe("handleFeishuMessage command authorization", () => {
|
|
|
247
265
|
await handleFeishuMessage({
|
|
248
266
|
cfg,
|
|
249
267
|
event,
|
|
250
|
-
runtime: {
|
|
268
|
+
runtime: {
|
|
269
|
+
log: vi.fn(),
|
|
270
|
+
error: vi.fn(),
|
|
271
|
+
exit: vi.fn((code: number): never => {
|
|
272
|
+
throw new Error(`exit ${code}`);
|
|
273
|
+
}),
|
|
274
|
+
} as RuntimeEnv,
|
|
251
275
|
});
|
|
252
276
|
|
|
253
277
|
expect(mockResolveCommandAuthorizedFromAuthorizers).toHaveBeenCalledWith({
|
package/src/bot.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
2
2
|
import {
|
|
3
|
+
buildAgentMediaPayload,
|
|
3
4
|
buildPendingHistoryContextFromMap,
|
|
4
5
|
recordPendingHistoryEntryIfEnabled,
|
|
5
6
|
clearHistoryEntriesIfEnabled,
|
|
6
7
|
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
7
8
|
type HistoryEntry,
|
|
8
9
|
} from "openclaw/plugin-sdk";
|
|
9
|
-
import type { FeishuMessageContext, FeishuMediaInfo, ResolvedFeishuAccount } from "./types.js";
|
|
10
|
-
import type { DynamicAgentCreationConfig } from "./types.js";
|
|
11
10
|
import { resolveFeishuAccount } from "./accounts.js";
|
|
12
11
|
import { createFeishuClient } from "./client.js";
|
|
13
12
|
import { tryRecordMessage } from "./dedup.js";
|
|
@@ -23,6 +22,8 @@ import {
|
|
|
23
22
|
import { createFeishuReplyDispatcher } from "./reply-dispatcher.js";
|
|
24
23
|
import { getFeishuRuntime } from "./runtime.js";
|
|
25
24
|
import { getMessageFeishu, sendMessageFeishu } from "./send.js";
|
|
25
|
+
import type { FeishuMessageContext, FeishuMediaInfo, ResolvedFeishuAccount } from "./types.js";
|
|
26
|
+
import type { DynamicAgentCreationConfig } from "./types.js";
|
|
26
27
|
|
|
27
28
|
// --- Permission error extraction ---
|
|
28
29
|
// Extract permission grant URL from Feishu API error response.
|
|
@@ -184,10 +185,17 @@ function parseMessageContent(content: string, messageType: string): string {
|
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
function checkBotMentioned(event: FeishuMessageEvent, botOpenId?: string): boolean {
|
|
187
|
-
const mentions = event.message.mentions ?? [];
|
|
188
|
-
if (mentions.length === 0) return false;
|
|
189
188
|
if (!botOpenId) return false;
|
|
190
|
-
|
|
189
|
+
const mentions = event.message.mentions ?? [];
|
|
190
|
+
if (mentions.length > 0) {
|
|
191
|
+
return mentions.some((m) => m.id.open_id === botOpenId);
|
|
192
|
+
}
|
|
193
|
+
// Post (rich text) messages may have empty message.mentions when they contain docs/paste
|
|
194
|
+
if (event.message.message_type === "post") {
|
|
195
|
+
const { mentionedOpenIds } = parsePostContent(event.message.content);
|
|
196
|
+
return mentionedOpenIds.some((id) => id === botOpenId);
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
191
199
|
}
|
|
192
200
|
|
|
193
201
|
function stripBotMention(
|
|
@@ -243,6 +251,7 @@ function parseMediaKeys(
|
|
|
243
251
|
function parsePostContent(content: string): {
|
|
244
252
|
textContent: string;
|
|
245
253
|
imageKeys: string[];
|
|
254
|
+
mentionedOpenIds: string[];
|
|
246
255
|
} {
|
|
247
256
|
try {
|
|
248
257
|
const parsed = JSON.parse(content);
|
|
@@ -250,6 +259,7 @@ function parsePostContent(content: string): {
|
|
|
250
259
|
const contentBlocks = parsed.content || [];
|
|
251
260
|
let textContent = title ? `${title}\n\n` : "";
|
|
252
261
|
const imageKeys: string[] = [];
|
|
262
|
+
const mentionedOpenIds: string[] = [];
|
|
253
263
|
|
|
254
264
|
for (const paragraph of contentBlocks) {
|
|
255
265
|
if (Array.isArray(paragraph)) {
|
|
@@ -262,6 +272,9 @@ function parsePostContent(content: string): {
|
|
|
262
272
|
} else if (element.tag === "at") {
|
|
263
273
|
// Mention: @username
|
|
264
274
|
textContent += `@${element.user_name || element.user_id || ""}`;
|
|
275
|
+
if (element.user_id) {
|
|
276
|
+
mentionedOpenIds.push(element.user_id);
|
|
277
|
+
}
|
|
265
278
|
} else if (element.tag === "img" && element.image_key) {
|
|
266
279
|
// Embedded image
|
|
267
280
|
imageKeys.push(element.image_key);
|
|
@@ -272,11 +285,12 @@ function parsePostContent(content: string): {
|
|
|
272
285
|
}
|
|
273
286
|
|
|
274
287
|
return {
|
|
275
|
-
textContent: textContent.trim() || "[
|
|
288
|
+
textContent: textContent.trim() || "[Rich text message]",
|
|
276
289
|
imageKeys,
|
|
290
|
+
mentionedOpenIds,
|
|
277
291
|
};
|
|
278
292
|
} catch {
|
|
279
|
-
return { textContent: "[
|
|
293
|
+
return { textContent: "[Rich text message]", imageKeys: [], mentionedOpenIds: [] };
|
|
280
294
|
}
|
|
281
295
|
}
|
|
282
296
|
|
|
@@ -433,27 +447,6 @@ async function resolveFeishuMediaList(params: {
|
|
|
433
447
|
* Build media payload for inbound context.
|
|
434
448
|
* Similar to Discord's buildDiscordMediaPayload().
|
|
435
449
|
*/
|
|
436
|
-
function buildFeishuMediaPayload(mediaList: FeishuMediaInfo[]): {
|
|
437
|
-
MediaPath?: string;
|
|
438
|
-
MediaType?: string;
|
|
439
|
-
MediaUrl?: string;
|
|
440
|
-
MediaPaths?: string[];
|
|
441
|
-
MediaUrls?: string[];
|
|
442
|
-
MediaTypes?: string[];
|
|
443
|
-
} {
|
|
444
|
-
const first = mediaList[0];
|
|
445
|
-
const mediaPaths = mediaList.map((media) => media.path);
|
|
446
|
-
const mediaTypes = mediaList.map((media) => media.contentType).filter(Boolean) as string[];
|
|
447
|
-
return {
|
|
448
|
-
MediaPath: first?.path,
|
|
449
|
-
MediaType: first?.contentType,
|
|
450
|
-
MediaUrl: first?.path,
|
|
451
|
-
MediaPaths: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
452
|
-
MediaUrls: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
453
|
-
MediaTypes: mediaTypes.length > 0 ? mediaTypes : undefined,
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
|
|
457
450
|
export function parseFeishuMessageEvent(
|
|
458
451
|
event: FeishuMessageEvent,
|
|
459
452
|
botOpenId?: string,
|
|
@@ -766,7 +759,7 @@ export async function handleFeishuMessage(params: {
|
|
|
766
759
|
log,
|
|
767
760
|
accountId: account.accountId,
|
|
768
761
|
});
|
|
769
|
-
const mediaPayload =
|
|
762
|
+
const mediaPayload = buildAgentMediaPayload(mediaList);
|
|
770
763
|
|
|
771
764
|
// Fetch quoted/replied message content if parentId exists
|
|
772
765
|
let quotedContent: string | undefined;
|
package/src/channel.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
buildBaseChannelStatusSummary,
|
|
4
|
+
createDefaultChannelRuntimeState,
|
|
5
|
+
DEFAULT_ACCOUNT_ID,
|
|
6
|
+
PAIRING_APPROVED_MESSAGE,
|
|
7
|
+
} from "openclaw/plugin-sdk";
|
|
4
8
|
import {
|
|
5
9
|
resolveFeishuAccount,
|
|
6
10
|
resolveFeishuCredentials,
|
|
@@ -19,6 +23,7 @@ import { resolveFeishuGroupToolPolicy } from "./policy.js";
|
|
|
19
23
|
import { probeFeishu } from "./probe.js";
|
|
20
24
|
import { sendMessageFeishu } from "./send.js";
|
|
21
25
|
import { normalizeFeishuTarget, looksLikeFeishuId, formatFeishuTarget } from "./targets.js";
|
|
26
|
+
import type { ResolvedFeishuAccount, FeishuConfig } from "./types.js";
|
|
22
27
|
|
|
23
28
|
const meta: ChannelMeta = {
|
|
24
29
|
id: "feishu",
|
|
@@ -303,20 +308,9 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
|
|
303
308
|
},
|
|
304
309
|
outbound: feishuOutbound,
|
|
305
310
|
status: {
|
|
306
|
-
defaultRuntime: {
|
|
307
|
-
accountId: DEFAULT_ACCOUNT_ID,
|
|
308
|
-
running: false,
|
|
309
|
-
lastStartAt: null,
|
|
310
|
-
lastStopAt: null,
|
|
311
|
-
lastError: null,
|
|
312
|
-
port: null,
|
|
313
|
-
},
|
|
311
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, { port: null }),
|
|
314
312
|
buildChannelSummary: ({ snapshot }) => ({
|
|
315
|
-
|
|
316
|
-
running: snapshot.running ?? false,
|
|
317
|
-
lastStartAt: snapshot.lastStartAt ?? null,
|
|
318
|
-
lastStopAt: snapshot.lastStopAt ?? null,
|
|
319
|
-
lastError: snapshot.lastError ?? null,
|
|
313
|
+
...buildBaseChannelStatusSummary(snapshot),
|
|
320
314
|
port: snapshot.port ?? null,
|
|
321
315
|
probe: snapshot.probe,
|
|
322
316
|
lastProbeAt: snapshot.lastProbeAt ?? null,
|
package/src/docx.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { Readable } from "stream";
|
|
1
2
|
import type * as Lark from "@larksuiteoapi/node-sdk";
|
|
2
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
|
-
import {
|
|
4
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
5
5
|
import { listEnabledFeishuAccounts } from "./accounts.js";
|
|
6
6
|
import { createFeishuClient } from "./client.js";
|
|
7
7
|
import { FeishuDocSchema, type FeishuDocParams } from "./doc-schema.js";
|
package/src/dynamic-agent.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
|
|
5
5
|
import type { DynamicAgentCreationConfig } from "./types.js";
|
|
6
6
|
|
|
7
7
|
export type MaybeCreateDynamicAgentResult = {
|
package/src/media.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
1
|
import fs from "fs";
|
|
3
2
|
import os from "os";
|
|
4
3
|
import path from "path";
|
|
5
4
|
import { Readable } from "stream";
|
|
5
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
6
6
|
import { resolveFeishuAccount } from "./accounts.js";
|
|
7
7
|
import { createFeishuClient } from "./client.js";
|
|
8
8
|
import { getFeishuRuntime } from "./runtime.js";
|
|
9
|
+
import { assertFeishuMessageApiSuccess, toFeishuSendResult } from "./send-result.js";
|
|
9
10
|
import { resolveReceiveIdType, normalizeFeishuTarget } from "./targets.js";
|
|
10
11
|
|
|
11
12
|
export type DownloadImageResult = {
|
|
@@ -283,15 +284,8 @@ export async function sendImageFeishu(params: {
|
|
|
283
284
|
msg_type: "image",
|
|
284
285
|
},
|
|
285
286
|
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
throw new Error(`Feishu image reply failed: ${response.msg || `code ${response.code}`}`);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return {
|
|
292
|
-
messageId: response.data?.message_id ?? "unknown",
|
|
293
|
-
chatId: receiveId,
|
|
294
|
-
};
|
|
287
|
+
assertFeishuMessageApiSuccess(response, "Feishu image reply failed");
|
|
288
|
+
return toFeishuSendResult(response, receiveId);
|
|
295
289
|
}
|
|
296
290
|
|
|
297
291
|
const response = await client.im.message.create({
|
|
@@ -302,15 +296,8 @@ export async function sendImageFeishu(params: {
|
|
|
302
296
|
msg_type: "image",
|
|
303
297
|
},
|
|
304
298
|
});
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
throw new Error(`Feishu image send failed: ${response.msg || `code ${response.code}`}`);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return {
|
|
311
|
-
messageId: response.data?.message_id ?? "unknown",
|
|
312
|
-
chatId: receiveId,
|
|
313
|
-
};
|
|
299
|
+
assertFeishuMessageApiSuccess(response, "Feishu image send failed");
|
|
300
|
+
return toFeishuSendResult(response, receiveId);
|
|
314
301
|
}
|
|
315
302
|
|
|
316
303
|
/**
|
|
@@ -349,15 +336,8 @@ export async function sendFileFeishu(params: {
|
|
|
349
336
|
msg_type: msgType,
|
|
350
337
|
},
|
|
351
338
|
});
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
throw new Error(`Feishu file reply failed: ${response.msg || `code ${response.code}`}`);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return {
|
|
358
|
-
messageId: response.data?.message_id ?? "unknown",
|
|
359
|
-
chatId: receiveId,
|
|
360
|
-
};
|
|
339
|
+
assertFeishuMessageApiSuccess(response, "Feishu file reply failed");
|
|
340
|
+
return toFeishuSendResult(response, receiveId);
|
|
361
341
|
}
|
|
362
342
|
|
|
363
343
|
const response = await client.im.message.create({
|
|
@@ -368,15 +348,8 @@ export async function sendFileFeishu(params: {
|
|
|
368
348
|
msg_type: msgType,
|
|
369
349
|
},
|
|
370
350
|
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
throw new Error(`Feishu file send failed: ${response.msg || `code ${response.code}`}`);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return {
|
|
377
|
-
messageId: response.data?.message_id ?? "unknown",
|
|
378
|
-
chatId: receiveId,
|
|
379
|
-
};
|
|
351
|
+
assertFeishuMessageApiSuccess(response, "Feishu file send failed");
|
|
352
|
+
return toFeishuSendResult(response, receiveId);
|
|
380
353
|
}
|
|
381
354
|
|
|
382
355
|
/**
|
package/src/monitor.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
2
1
|
import * as http from "http";
|
|
2
|
+
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
3
3
|
import {
|
|
4
4
|
type ClawdbotConfig,
|
|
5
5
|
type RuntimeEnv,
|
|
6
6
|
type HistoryEntry,
|
|
7
7
|
installRequestBodyLimitGuard,
|
|
8
8
|
} from "openclaw/plugin-sdk";
|
|
9
|
-
import type { ResolvedFeishuAccount } from "./types.js";
|
|
10
9
|
import { resolveFeishuAccount, listEnabledFeishuAccounts } from "./accounts.js";
|
|
11
10
|
import { handleFeishuMessage, type FeishuMessageEvent, type FeishuBotAddedEvent } from "./bot.js";
|
|
12
11
|
import { createFeishuWSClient, createEventDispatcher } from "./client.js";
|
|
13
12
|
import { probeFeishu } from "./probe.js";
|
|
13
|
+
import type { ResolvedFeishuAccount } from "./types.js";
|
|
14
14
|
|
|
15
15
|
export type MonitorFeishuOpts = {
|
|
16
16
|
config?: ClawdbotConfig;
|
package/src/onboarding.ts
CHANGED
|
@@ -6,9 +6,9 @@ import type {
|
|
|
6
6
|
WizardPrompter,
|
|
7
7
|
} from "openclaw/plugin-sdk";
|
|
8
8
|
import { addWildcardAllowFrom, DEFAULT_ACCOUNT_ID, formatDocsLink } from "openclaw/plugin-sdk";
|
|
9
|
-
import type { FeishuConfig } from "./types.js";
|
|
10
9
|
import { resolveFeishuCredentials } from "./accounts.js";
|
|
11
10
|
import { probeFeishu } from "./probe.js";
|
|
11
|
+
import type { FeishuConfig } from "./types.js";
|
|
12
12
|
|
|
13
13
|
const channel = "feishu" as const;
|
|
14
14
|
|
package/src/policy.ts
CHANGED
|
@@ -1,39 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AllowlistMatch,
|
|
3
|
+
ChannelGroupContext,
|
|
4
|
+
GroupToolPolicyConfig,
|
|
5
|
+
} from "openclaw/plugin-sdk";
|
|
6
|
+
import { resolveAllowlistMatchSimple } from "openclaw/plugin-sdk";
|
|
2
7
|
import type { FeishuConfig, FeishuGroupConfig } from "./types.js";
|
|
3
8
|
|
|
4
|
-
export type FeishuAllowlistMatch =
|
|
5
|
-
allowed: boolean;
|
|
6
|
-
matchKey?: string;
|
|
7
|
-
matchSource?: "wildcard" | "id" | "name";
|
|
8
|
-
};
|
|
9
|
+
export type FeishuAllowlistMatch = AllowlistMatch<"wildcard" | "id" | "name">;
|
|
9
10
|
|
|
10
11
|
export function resolveFeishuAllowlistMatch(params: {
|
|
11
12
|
allowFrom: Array<string | number>;
|
|
12
13
|
senderId: string;
|
|
13
14
|
senderName?: string | null;
|
|
14
15
|
}): FeishuAllowlistMatch {
|
|
15
|
-
|
|
16
|
-
.map((entry) => String(entry).trim().toLowerCase())
|
|
17
|
-
.filter(Boolean);
|
|
18
|
-
|
|
19
|
-
if (allowFrom.length === 0) {
|
|
20
|
-
return { allowed: false };
|
|
21
|
-
}
|
|
22
|
-
if (allowFrom.includes("*")) {
|
|
23
|
-
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const senderId = params.senderId.toLowerCase();
|
|
27
|
-
if (allowFrom.includes(senderId)) {
|
|
28
|
-
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const senderName = params.senderName?.toLowerCase();
|
|
32
|
-
if (senderName && allowFrom.includes(senderName)) {
|
|
33
|
-
return { allowed: true, matchKey: senderName, matchSource: "name" };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { allowed: false };
|
|
16
|
+
return resolveAllowlistMatchSimple(params);
|
|
37
17
|
}
|
|
38
18
|
|
|
39
19
|
export function resolveFeishuGroupConfig(params: {
|
package/src/probe.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { FeishuProbeResult } from "./types.js";
|
|
2
1
|
import { createFeishuClient, type FeishuClientCredentials } from "./client.js";
|
|
2
|
+
import type { FeishuProbeResult } from "./types.js";
|
|
3
3
|
|
|
4
4
|
export async function probeFeishu(creds?: FeishuClientCredentials): Promise<FeishuProbeResult> {
|
|
5
5
|
if (!creds?.appId || !creds?.appSecret) {
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
type ReplyPayload,
|
|
7
7
|
type RuntimeEnv,
|
|
8
8
|
} from "openclaw/plugin-sdk";
|
|
9
|
-
import type { MentionTarget } from "./mention.js";
|
|
10
9
|
import { resolveFeishuAccount } from "./accounts.js";
|
|
11
10
|
import { createFeishuClient } from "./client.js";
|
|
11
|
+
import type { MentionTarget } from "./mention.js";
|
|
12
12
|
import { buildMentionedCardContent } from "./mention.js";
|
|
13
13
|
import { getFeishuRuntime } from "./runtime.js";
|
|
14
14
|
import { sendMarkdownCardFeishu, sendMessageFeishu } from "./send.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type FeishuMessageApiResponse = {
|
|
2
|
+
code?: number;
|
|
3
|
+
msg?: string;
|
|
4
|
+
data?: {
|
|
5
|
+
message_id?: string;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function assertFeishuMessageApiSuccess(
|
|
10
|
+
response: FeishuMessageApiResponse,
|
|
11
|
+
errorPrefix: string,
|
|
12
|
+
) {
|
|
13
|
+
if (response.code !== 0) {
|
|
14
|
+
throw new Error(`${errorPrefix}: ${response.msg || `code ${response.code}`}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function toFeishuSendResult(
|
|
19
|
+
response: FeishuMessageApiResponse,
|
|
20
|
+
chatId: string,
|
|
21
|
+
): {
|
|
22
|
+
messageId: string;
|
|
23
|
+
chatId: string;
|
|
24
|
+
} {
|
|
25
|
+
return {
|
|
26
|
+
messageId: response.data?.message_id ?? "unknown",
|
|
27
|
+
chatId,
|
|
28
|
+
};
|
|
29
|
+
}
|
package/src/send.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
-
import type { MentionTarget } from "./mention.js";
|
|
3
|
-
import type { FeishuSendResult, ResolvedFeishuAccount } from "./types.js";
|
|
4
2
|
import { resolveFeishuAccount } from "./accounts.js";
|
|
5
3
|
import { createFeishuClient } from "./client.js";
|
|
4
|
+
import type { MentionTarget } from "./mention.js";
|
|
6
5
|
import { buildMentionedMessage, buildMentionedCardContent } from "./mention.js";
|
|
7
6
|
import { getFeishuRuntime } from "./runtime.js";
|
|
7
|
+
import { assertFeishuMessageApiSuccess, toFeishuSendResult } from "./send-result.js";
|
|
8
8
|
import { resolveReceiveIdType, normalizeFeishuTarget } from "./targets.js";
|
|
9
|
+
import type { FeishuSendResult, ResolvedFeishuAccount } from "./types.js";
|
|
9
10
|
|
|
10
11
|
export type FeishuMessageInfo = {
|
|
11
12
|
messageId: string;
|
|
@@ -161,15 +162,8 @@ export async function sendMessageFeishu(
|
|
|
161
162
|
msg_type: msgType,
|
|
162
163
|
},
|
|
163
164
|
});
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
throw new Error(`Feishu reply failed: ${response.msg || `code ${response.code}`}`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
messageId: response.data?.message_id ?? "unknown",
|
|
171
|
-
chatId: receiveId,
|
|
172
|
-
};
|
|
165
|
+
assertFeishuMessageApiSuccess(response, "Feishu reply failed");
|
|
166
|
+
return toFeishuSendResult(response, receiveId);
|
|
173
167
|
}
|
|
174
168
|
|
|
175
169
|
const response = await client.im.message.create({
|
|
@@ -180,15 +174,8 @@ export async function sendMessageFeishu(
|
|
|
180
174
|
msg_type: msgType,
|
|
181
175
|
},
|
|
182
176
|
});
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
throw new Error(`Feishu send failed: ${response.msg || `code ${response.code}`}`);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
messageId: response.data?.message_id ?? "unknown",
|
|
190
|
-
chatId: receiveId,
|
|
191
|
-
};
|
|
177
|
+
assertFeishuMessageApiSuccess(response, "Feishu send failed");
|
|
178
|
+
return toFeishuSendResult(response, receiveId);
|
|
192
179
|
}
|
|
193
180
|
|
|
194
181
|
export type SendFeishuCardParams = {
|
|
@@ -223,15 +210,8 @@ export async function sendCardFeishu(params: SendFeishuCardParams): Promise<Feis
|
|
|
223
210
|
msg_type: "interactive",
|
|
224
211
|
},
|
|
225
212
|
});
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
throw new Error(`Feishu card reply failed: ${response.msg || `code ${response.code}`}`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
messageId: response.data?.message_id ?? "unknown",
|
|
233
|
-
chatId: receiveId,
|
|
234
|
-
};
|
|
213
|
+
assertFeishuMessageApiSuccess(response, "Feishu card reply failed");
|
|
214
|
+
return toFeishuSendResult(response, receiveId);
|
|
235
215
|
}
|
|
236
216
|
|
|
237
217
|
const response = await client.im.message.create({
|
|
@@ -242,15 +222,8 @@ export async function sendCardFeishu(params: SendFeishuCardParams): Promise<Feis
|
|
|
242
222
|
msg_type: "interactive",
|
|
243
223
|
},
|
|
244
224
|
});
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
throw new Error(`Feishu card send failed: ${response.msg || `code ${response.code}`}`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
messageId: response.data?.message_id ?? "unknown",
|
|
252
|
-
chatId: receiveId,
|
|
253
|
-
};
|
|
225
|
+
assertFeishuMessageApiSuccess(response, "Feishu card send failed");
|
|
226
|
+
return toFeishuSendResult(response, receiveId);
|
|
254
227
|
}
|
|
255
228
|
|
|
256
229
|
export async function updateCardFeishu(params: {
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BaseProbeResult } from "openclaw/plugin-sdk";
|
|
1
2
|
import type {
|
|
2
3
|
FeishuConfigSchema,
|
|
3
4
|
FeishuGroupSchema,
|
|
@@ -52,9 +53,7 @@ export type FeishuSendResult = {
|
|
|
52
53
|
chatId: string;
|
|
53
54
|
};
|
|
54
55
|
|
|
55
|
-
export type FeishuProbeResult = {
|
|
56
|
-
ok: boolean;
|
|
57
|
-
error?: string;
|
|
56
|
+
export type FeishuProbeResult = BaseProbeResult<string> & {
|
|
58
57
|
appId?: string;
|
|
59
58
|
botName?: string;
|
|
60
59
|
botOpenId?: string;
|