chattercatcher 0.1.26 → 0.1.27
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/dist/cli.js +145 -42
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +132 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -134,6 +134,7 @@ interface IngestMessageInput {
|
|
|
134
134
|
interface CreateImageSummaryMessageInput {
|
|
135
135
|
sourceMessageId: string;
|
|
136
136
|
imageKey: string;
|
|
137
|
+
imageFileName?: string;
|
|
137
138
|
summary: string;
|
|
138
139
|
reason?: string;
|
|
139
140
|
multimodalModel: string;
|
|
@@ -237,6 +238,7 @@ interface CronJobRecord {
|
|
|
237
238
|
createdByOpenId?: string;
|
|
238
239
|
schedule: string;
|
|
239
240
|
prompt: string;
|
|
241
|
+
imageFileName?: string;
|
|
240
242
|
status: CronJobStatus;
|
|
241
243
|
lastRunAt?: string;
|
|
242
244
|
nextRunAt: string;
|
|
@@ -256,6 +258,7 @@ declare class CronJobRepository {
|
|
|
256
258
|
createdByOpenId?: string;
|
|
257
259
|
schedule: string;
|
|
258
260
|
prompt: string;
|
|
261
|
+
imageFileName?: string;
|
|
259
262
|
}): CronJobRecord;
|
|
260
263
|
get(id: string): CronJobRecord | null;
|
|
261
264
|
list(limit?: number): CronJobRecord[];
|
|
@@ -280,6 +283,7 @@ interface CreateCronJobSchedulerOptions {
|
|
|
280
283
|
repository: Pick<CronJobRepository, "listDue" | "markSuccess" | "markFailure">;
|
|
281
284
|
generateMessage: (job: CronJobRecord, now: Date) => Promise<string>;
|
|
282
285
|
sendTextToChat: (chatId: string, text: string) => Promise<void>;
|
|
286
|
+
sendImageToChat?: (chatId: string, imageFileName: string) => Promise<void>;
|
|
283
287
|
now?: () => Date;
|
|
284
288
|
setIntervalFn?: typeof setInterval;
|
|
285
289
|
clearIntervalFn?: typeof clearInterval;
|
|
@@ -594,6 +598,7 @@ interface IndexingScheduler {
|
|
|
594
598
|
|
|
595
599
|
interface MessageSender {
|
|
596
600
|
sendTextToChat(chatId: string, text: string): Promise<void>;
|
|
601
|
+
sendImageToChat?(chatId: string, imagePath: string): Promise<void>;
|
|
597
602
|
replyTextToMessage?(messageId: string, text: string): Promise<void>;
|
|
598
603
|
addReactionToMessage?(messageId: string, emojiType: string): Promise<void>;
|
|
599
604
|
}
|
|
@@ -621,6 +626,14 @@ interface FeishuClientLike {
|
|
|
621
626
|
};
|
|
622
627
|
}) => Promise<unknown>;
|
|
623
628
|
};
|
|
629
|
+
image?: {
|
|
630
|
+
create(payload: {
|
|
631
|
+
data: {
|
|
632
|
+
image_type: "message";
|
|
633
|
+
image: Buffer;
|
|
634
|
+
};
|
|
635
|
+
}): Promise<unknown>;
|
|
636
|
+
};
|
|
624
637
|
messageReaction?: {
|
|
625
638
|
create(payload: {
|
|
626
639
|
path: {
|
|
@@ -663,6 +676,7 @@ declare class FeishuMessageSender implements MessageSender {
|
|
|
663
676
|
constructor(client: FeishuClientLike);
|
|
664
677
|
static fromConfig(config: AppConfig, secrets: AppSecrets): FeishuMessageSender;
|
|
665
678
|
sendTextToChat(chatId: string, text: string): Promise<void>;
|
|
679
|
+
sendImageToChat(chatId: string, imagePath: string): Promise<void>;
|
|
666
680
|
replyTextToMessage(messageId: string, text: string): Promise<void>;
|
|
667
681
|
addReactionToMessage(messageId: string, emojiType: string): Promise<void>;
|
|
668
682
|
}
|
|
@@ -731,7 +745,7 @@ interface FeishuGatewayOptions {
|
|
|
731
745
|
cronJobProcessor?: {
|
|
732
746
|
database: SqliteDatabase;
|
|
733
747
|
model: ChatModel;
|
|
734
|
-
sender: Pick<MessageSender, "sendTextToChat">;
|
|
748
|
+
sender: Pick<MessageSender, "sendTextToChat" | "sendImageToChat">;
|
|
735
749
|
};
|
|
736
750
|
cronJobScheduler?: CronJobScheduler;
|
|
737
751
|
wsClientFactory?: (params: {
|
package/dist/index.js
CHANGED
|
@@ -362,6 +362,7 @@ var CronJobRepository = class {
|
|
|
362
362
|
create(input) {
|
|
363
363
|
const schedule = input.schedule.trim();
|
|
364
364
|
const prompt = input.prompt.trim();
|
|
365
|
+
const imageFileName = input.imageFileName?.trim();
|
|
365
366
|
if (!isValidCronSchedule(schedule)) {
|
|
366
367
|
throw new Error("cron \u8868\u8FBE\u5F0F\u65E0\u6548\u3002");
|
|
367
368
|
}
|
|
@@ -379,6 +380,7 @@ var CronJobRepository = class {
|
|
|
379
380
|
createdByOpenId: input.createdByOpenId,
|
|
380
381
|
schedule,
|
|
381
382
|
prompt,
|
|
383
|
+
...imageFileName ? { imageFileName } : {},
|
|
382
384
|
status: "active",
|
|
383
385
|
nextRunAt: nextRunAt.toISOString(),
|
|
384
386
|
createdAt: now.toISOString(),
|
|
@@ -387,15 +389,18 @@ var CronJobRepository = class {
|
|
|
387
389
|
this.database.prepare(
|
|
388
390
|
`
|
|
389
391
|
INSERT INTO cron_jobs (
|
|
390
|
-
id, chat_id, created_by_open_id, schedule, prompt, status,
|
|
392
|
+
id, chat_id, created_by_open_id, schedule, prompt, image_file_name, status,
|
|
391
393
|
last_run_at, next_run_at, last_error, created_at, updated_at
|
|
392
394
|
)
|
|
393
395
|
VALUES (
|
|
394
|
-
@id, @chatId, @createdByOpenId, @schedule, @prompt, @status,
|
|
396
|
+
@id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName, @status,
|
|
395
397
|
NULL, @nextRunAt, NULL, @createdAt, @updatedAt
|
|
396
398
|
)
|
|
397
399
|
`
|
|
398
|
-
).run(
|
|
400
|
+
).run({
|
|
401
|
+
...record,
|
|
402
|
+
imageFileName: record.imageFileName ?? null
|
|
403
|
+
});
|
|
399
404
|
return record;
|
|
400
405
|
}
|
|
401
406
|
get(id) {
|
|
@@ -420,6 +425,7 @@ var CronJobRepository = class {
|
|
|
420
425
|
created_by_open_id AS createdByOpenId,
|
|
421
426
|
schedule,
|
|
422
427
|
prompt,
|
|
428
|
+
image_file_name AS imageFileName,
|
|
423
429
|
status,
|
|
424
430
|
last_run_at AS lastRunAt,
|
|
425
431
|
next_run_at AS nextRunAt,
|
|
@@ -438,6 +444,7 @@ var CronJobRepository = class {
|
|
|
438
444
|
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
439
445
|
schedule: row.schedule,
|
|
440
446
|
prompt: row.prompt,
|
|
447
|
+
imageFileName: row.imageFileName ?? void 0,
|
|
441
448
|
status: row.status,
|
|
442
449
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
443
450
|
nextRunAt: row.nextRunAt,
|
|
@@ -511,6 +518,7 @@ var CronJobRepository = class {
|
|
|
511
518
|
created_by_open_id AS createdByOpenId,
|
|
512
519
|
schedule,
|
|
513
520
|
prompt,
|
|
521
|
+
image_file_name AS imageFileName,
|
|
514
522
|
status,
|
|
515
523
|
last_run_at AS lastRunAt,
|
|
516
524
|
next_run_at AS nextRunAt,
|
|
@@ -529,6 +537,7 @@ var CronJobRepository = class {
|
|
|
529
537
|
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
530
538
|
schedule: row.schedule,
|
|
531
539
|
prompt: row.prompt,
|
|
540
|
+
imageFileName: row.imageFileName ?? void 0,
|
|
532
541
|
status: row.status,
|
|
533
542
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
534
543
|
nextRunAt: row.nextRunAt,
|
|
@@ -559,6 +568,12 @@ function createCronJobScheduler(options) {
|
|
|
559
568
|
try {
|
|
560
569
|
const text = await options.generateMessage(job, startedAt);
|
|
561
570
|
await options.sendTextToChat(job.chatId, text);
|
|
571
|
+
if (job.imageFileName) {
|
|
572
|
+
if (!options.sendImageToChat) {
|
|
573
|
+
throw new Error("\u5F53\u524D\u5B9A\u65F6\u4EFB\u52A1\u8FD0\u884C\u73AF\u5883\u4E0D\u652F\u6301\u53D1\u9001\u56FE\u7247\u3002");
|
|
574
|
+
}
|
|
575
|
+
await options.sendImageToChat(job.chatId, job.imageFileName);
|
|
576
|
+
}
|
|
562
577
|
options.repository.markSuccess(job.id, startedAt);
|
|
563
578
|
} catch (error) {
|
|
564
579
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -599,6 +614,17 @@ function readString(input, key) {
|
|
|
599
614
|
}
|
|
600
615
|
return value.trim();
|
|
601
616
|
}
|
|
617
|
+
function readOptionalString(input, key) {
|
|
618
|
+
const value = typeof input === "object" && input !== null && key in input ? input[key] : void 0;
|
|
619
|
+
if (value === void 0 || value === null) {
|
|
620
|
+
return void 0;
|
|
621
|
+
}
|
|
622
|
+
if (typeof value !== "string") {
|
|
623
|
+
throw new Error(`${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002`);
|
|
624
|
+
}
|
|
625
|
+
const trimmed = value.trim();
|
|
626
|
+
return trimmed || void 0;
|
|
627
|
+
}
|
|
602
628
|
function createCronJobTools(input) {
|
|
603
629
|
return [
|
|
604
630
|
{
|
|
@@ -614,6 +640,10 @@ function createCronJobTools(input) {
|
|
|
614
640
|
prompt: {
|
|
615
641
|
type: "string",
|
|
616
642
|
description: "Prompt used later to generate the scheduled message."
|
|
643
|
+
},
|
|
644
|
+
imageFileName: {
|
|
645
|
+
type: "string",
|
|
646
|
+
description: "Optional image filename already stored from the current chat, for example om_xxx-image.jpg."
|
|
617
647
|
}
|
|
618
648
|
},
|
|
619
649
|
required: ["schedule", "prompt"],
|
|
@@ -624,7 +654,8 @@ function createCronJobTools(input) {
|
|
|
624
654
|
chatId: input.chatId,
|
|
625
655
|
createdByOpenId: input.createdByOpenId,
|
|
626
656
|
schedule: readString(rawInput, "schedule"),
|
|
627
|
-
prompt: readString(rawInput, "prompt")
|
|
657
|
+
prompt: readString(rawInput, "prompt"),
|
|
658
|
+
imageFileName: readOptionalString(rawInput, "imageFileName")
|
|
628
659
|
});
|
|
629
660
|
return JSON.stringify({ ok: true, job });
|
|
630
661
|
}
|
|
@@ -954,12 +985,17 @@ function migrateDatabase(database) {
|
|
|
954
985
|
next_run_at TEXT NOT NULL,
|
|
955
986
|
last_error TEXT,
|
|
956
987
|
created_at TEXT NOT NULL,
|
|
957
|
-
updated_at TEXT NOT NULL
|
|
988
|
+
updated_at TEXT NOT NULL,
|
|
989
|
+
image_file_name TEXT
|
|
958
990
|
);
|
|
959
991
|
|
|
960
992
|
CREATE INDEX IF NOT EXISTS cron_jobs_chat_status_idx ON cron_jobs(chat_id, status, updated_at);
|
|
961
993
|
CREATE INDEX IF NOT EXISTS cron_jobs_due_idx ON cron_jobs(status, next_run_at);
|
|
962
994
|
`);
|
|
995
|
+
const cronJobColumns = database.prepare("PRAGMA table_info(cron_jobs)").all();
|
|
996
|
+
if (!cronJobColumns.some((column) => column.name === "image_file_name")) {
|
|
997
|
+
database.prepare("ALTER TABLE cron_jobs ADD COLUMN image_file_name TEXT").run();
|
|
998
|
+
}
|
|
963
999
|
}
|
|
964
1000
|
|
|
965
1001
|
// src/doctor/checks.ts
|
|
@@ -1710,6 +1746,9 @@ var MessageRepository = class {
|
|
|
1710
1746
|
throw new Error("\u539F\u59CB\u56FE\u7247\u6D88\u606F\u4E0D\u5B58\u5728\u3002");
|
|
1711
1747
|
}
|
|
1712
1748
|
const derivedPlatformMessageId = `${source.platformMessageId}:image-summary:${input.imageKey}`;
|
|
1749
|
+
const imageFileName = input.imageFileName?.trim();
|
|
1750
|
+
const summaryText = imageFileName ? `[\u56FE\u7247\u8F6C\u8FF0] \u6587\u4EF6\u540D\uFF1A${imageFileName}
|
|
1751
|
+
${input.summary.trim()}` : `[\u56FE\u7247\u8F6C\u8FF0] ${input.summary.trim()}`;
|
|
1713
1752
|
return this.ingest({
|
|
1714
1753
|
platform: source.platform,
|
|
1715
1754
|
platformChatId: source.platformChatId,
|
|
@@ -1718,12 +1757,13 @@ var MessageRepository = class {
|
|
|
1718
1757
|
senderId: source.senderId,
|
|
1719
1758
|
senderName: source.senderName,
|
|
1720
1759
|
messageType: "image_summary",
|
|
1721
|
-
text:
|
|
1760
|
+
text: summaryText,
|
|
1722
1761
|
sentAt: source.sentAt,
|
|
1723
1762
|
rawPayload: {
|
|
1724
1763
|
derivedFromMessageId: input.sourceMessageId,
|
|
1725
1764
|
sourceAttachmentKind: "image",
|
|
1726
1765
|
sourceResourceKey: input.imageKey,
|
|
1766
|
+
...imageFileName ? { imageFileName } : {},
|
|
1727
1767
|
multimodalModel: input.multimodalModel,
|
|
1728
1768
|
isMeaningful: true,
|
|
1729
1769
|
...input.reason?.trim() ? { reason: input.reason.trim() } : {},
|
|
@@ -3024,7 +3064,7 @@ async function summarizeEpisodeWindow(window, model, now) {
|
|
|
3024
3064
|
const summary = await model.complete([
|
|
3025
3065
|
{
|
|
3026
3066
|
role: "system",
|
|
3027
|
-
content: "\u4F60\u662F ChatterCatcher \u7684\u4F1A\u8BDD\u8BB0\u5FC6\u6574\u7406\u6A21\u5757\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u628A\u788E\u7247\u5316\u95F2\u804A\u6574\u7406\u6210\u53EF\u68C0\u7D22\u4E8B\u5B9E\uFF0C\u8865\u5168\u77ED\u6D88\u606F\u3001\u4EE3\u8BCD\u3001\u7F29\u5199\u4E0E\u4E0A\u4E0B\u6587\u4E4B\u95F4\u7684\u5173\u7CFB\u3002\u53EA\u603B\u7ED3\u660E\u786E\u4E8B\u5B9E\uFF0C\u4E0D\u8981\u7F16\u9020\u3002\u4FDD\u7559\u91CD\u8981\u6570\u5B57\u3001\u65E5\u671F\u3001\u94FE\u63A5\u548C\u4EE3\u7801\uFF1B\u5982\u679C\u5185\u5BB9\u50CF\u5BC6\u7801\u3001API key\u3001token \u6216\u5BC6\u94A5\uFF0C\u53EA\u63CF\u8FF0\u5176\u4E0A\u4E0B\u6587\u5173\u7CFB\uFF0C\u4E0D\u8981\u5728\u6458\u8981\u4E2D\u590D\u5199\u539F\u6587\u3002\u6D88\u606F\u91CC\u7684\u201C\u4ECA\u5929\u201D\u201C\u660E\u5929\u201D\u201C\u6628\u665A\u201D\u201C\u4E0B\u5468\u4E09\u201D\u7B49\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF0C\u8BF7\u57FA\u4E8E\u6BCF\u6761\u6D88\u606F\u524D\u7684\u53D1\u9001\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\u5199\u5165\u6458\u8981\u3002\u4F8B\u5982 [2026-05-05T20:00:00.000Z] \u5988\u5988\u8BF4\u201C\u660E\u5929\u8981\u7528\u4E1D\u4E1D\u9732\u201D\uFF0C\u6458\u8981\u5E94\u5199\u4E3A\u201C2026-05-06 \u8981\u7528\u4E1D\u4E1D\u9732\u201D\u3002"
|
|
3067
|
+
content: "\u4F60\u662F ChatterCatcher \u7684\u4F1A\u8BDD\u8BB0\u5FC6\u6574\u7406\u6A21\u5757\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u628A\u788E\u7247\u5316\u95F2\u804A\u6574\u7406\u6210\u53EF\u68C0\u7D22\u4E8B\u5B9E\uFF0C\u8865\u5168\u77ED\u6D88\u606F\u3001\u4EE3\u8BCD\u3001\u7F29\u5199\u4E0E\u4E0A\u4E0B\u6587\u4E4B\u95F4\u7684\u5173\u7CFB\u3002\u53EA\u603B\u7ED3\u660E\u786E\u4E8B\u5B9E\uFF0C\u4E0D\u8981\u7F16\u9020\u3002\u4FDD\u7559\u91CD\u8981\u6570\u5B57\u3001\u65E5\u671F\u3001\u94FE\u63A5\u3001\u6587\u4EF6\u540D\u548C\u4EE3\u7801\uFF1B\u5982\u679C\u56FE\u7247\u8F6C\u8FF0\u91CC\u51FA\u73B0\u6587\u4EF6\u540D\uFF0C\u5FC5\u987B\u5728\u6458\u8981\u4E2D\u539F\u6837\u4FDD\u7559\u8BE5\u6587\u4EF6\u540D\uFF0C\u65B9\u4FBF\u4E4B\u540E\u6309\u6587\u4EF6\u540D\u627E\u56DE\u56FE\u7247\u3002\u5982\u679C\u5185\u5BB9\u50CF\u5BC6\u7801\u3001API key\u3001token \u6216\u5BC6\u94A5\uFF0C\u53EA\u63CF\u8FF0\u5176\u4E0A\u4E0B\u6587\u5173\u7CFB\uFF0C\u4E0D\u8981\u5728\u6458\u8981\u4E2D\u590D\u5199\u539F\u6587\u3002\u6D88\u606F\u91CC\u7684\u201C\u4ECA\u5929\u201D\u201C\u660E\u5929\u201D\u201C\u6628\u665A\u201D\u201C\u4E0B\u5468\u4E09\u201D\u7B49\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF0C\u8BF7\u57FA\u4E8E\u6BCF\u6761\u6D88\u606F\u524D\u7684\u53D1\u9001\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\u5199\u5165\u6458\u8981\u3002\u4F8B\u5982 [2026-05-05T20:00:00.000Z] \u5988\u5988\u8BF4\u201C\u660E\u5929\u8981\u7528\u4E1D\u4E1D\u9732\u201D\uFF0C\u6458\u8981\u5E94\u5199\u4E3A\u201C2026-05-06 \u8981\u7528\u4E1D\u4E1D\u9732\u201D\u3002"
|
|
3028
3068
|
},
|
|
3029
3069
|
{
|
|
3030
3070
|
role: "user",
|
|
@@ -3055,6 +3095,7 @@ async function processEpisodesNow(input) {
|
|
|
3055
3095
|
|
|
3056
3096
|
// src/feishu/gateway.ts
|
|
3057
3097
|
import * as lark2 from "@larksuiteoapi/node-sdk";
|
|
3098
|
+
import path12 from "path";
|
|
3058
3099
|
|
|
3059
3100
|
// src/gateway/indexing-scheduler.ts
|
|
3060
3101
|
function createIndexingScheduler(options) {
|
|
@@ -3440,6 +3481,7 @@ var ImageMultimodalTaskRepository = class {
|
|
|
3440
3481
|
};
|
|
3441
3482
|
|
|
3442
3483
|
// src/multimodal/worker.ts
|
|
3484
|
+
import path11 from "path";
|
|
3443
3485
|
var ImageMultimodalWorker = class {
|
|
3444
3486
|
constructor(options) {
|
|
3445
3487
|
this.options = options;
|
|
@@ -3466,9 +3508,11 @@ var ImageMultimodalWorker = class {
|
|
|
3466
3508
|
throw error;
|
|
3467
3509
|
}
|
|
3468
3510
|
try {
|
|
3511
|
+
const imageFileName = path11.basename(running.storedPath);
|
|
3469
3512
|
const described = await this.options.model.describeImage({
|
|
3470
3513
|
imagePath: running.storedPath,
|
|
3471
|
-
mimeType: running.mimeType
|
|
3514
|
+
mimeType: running.mimeType,
|
|
3515
|
+
context: `\u56FE\u7247\u6587\u4EF6\u540D\uFF1A${imageFileName}`
|
|
3472
3516
|
});
|
|
3473
3517
|
if (!described.isMeaningful) {
|
|
3474
3518
|
this.options.tasks.markSkipped(running.id, described.reason || "\u591A\u6A21\u6001\u6A21\u578B\u5224\u5B9A\u56FE\u7247\u65E0\u610F\u4E49\u3002");
|
|
@@ -3478,6 +3522,7 @@ var ImageMultimodalWorker = class {
|
|
|
3478
3522
|
const derivedMessageId = this.options.messages.createImageSummaryMessage({
|
|
3479
3523
|
sourceMessageId: running.sourceMessageId,
|
|
3480
3524
|
imageKey: running.imageKey,
|
|
3525
|
+
imageFileName,
|
|
3481
3526
|
summary: described.summary,
|
|
3482
3527
|
reason: described.reason,
|
|
3483
3528
|
multimodalModel: this.options.multimodalModelName,
|
|
@@ -3628,7 +3673,7 @@ function stripMentions(text, mentions) {
|
|
|
3628
3673
|
}
|
|
3629
3674
|
return result.replace(/@/g, " ").replace(/\s+/g, " ").trim();
|
|
3630
3675
|
}
|
|
3631
|
-
var FEISHU_TOOL_SYSTEM_PROMPT = `\u4F60\u662F\u98DE\u4E66\u7FA4\u804A\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u5148\u641C\u7D22\u672C\u5730\u77E5\u8BC6\u6765\u56DE\u7B54\u95EE\u9898\uFF1B\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u521B\u5EFA\u3001\u67E5\u770B\u6216\u5220\u9664\u7FA4\u6D88\u606F\u5B9A\u65F6\u4EFB\u52A1\u65F6\uFF0C\u4E5F\u53EF\u4EE5\u8C03\u7528\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u3002\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u53EA\u7BA1\u7406\u5F53\u524D\u7FA4\u804A\uFF0C\u4E0D\u80FD\u8DE8\u7FA4\u64CD\u4F5C\u3002\u82E5\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u65F6\u95F4\uFF0C\u4F60\u9700\u8981\u5148\u5C06\u5176\u8F6C\u6362\u4E3A\u4E94\u5B57\u6BB5 cron \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u518D\u8C03\u7528\u5DE5\u5177\u3002\u5F53\u524D\u65F6\u95F4\u4F1A\u63D0\u4F9B\u7ED9\u4F60\u3002\u68C0\u7D22\u8BC1\u636E\u4E2D\u7684\u65F6\u95F4\u6233\u662F\u6D88\u606F\u88AB\u53D1\u9001\u65F6\u7684\u771F\u5B9E\u65F6\u95F4\u3002\u56DE\u7B54\u65F6\u82E5\u6D89\u53CA\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF08\u5982\u6D88\u606F\u4E2D\u8BF4\u201D\u660E\u5929\u201D\u201D\u4ECA\u665A\u201D\uFF09\uFF0C\u5FC5\u987B\u57FA\u4E8E\u8BC1\u636E\u4E2D\u6BCF\u6761\u6D88\u606F\u7684\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\uFF0C\u4E0D\u8981\u7167\u642C\u539F\u6587\u7684\u76F8\u5BF9\u8868\u8FF0\u3002\u5BF9\u4E8E\u4E00\u822C\u95EE\u7B54\uFF0C\u5148\u6309\u9700\u8C03\u7528\u641C\u7D22\u5DE5\u5177\uFF0C\u518D\u57FA\u4E8E\u5DE5\u5177\u8FD4\u56DE\u7684\u8BC1\u636E\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\uFF1B\u82E5\u5F15\u7528\u4E86\u68C0\u7D22\u7ED3\u679C\uFF0C\u8981\u5728\u7B54\u6848\u91CC\u76F4\u63A5\u5199\u51FA\u5F15\u7528\u5185\u5BB9\u3002\u4E0D\u8981\u58F0\u79F0\u5B8C\u6210\u4E86\u672A\u5B9E\u9645\u8C03\u7528\u7684\u64CD\u4F5C\u3002\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u7B54\u5FC5\u987B\u662F\u9762\u5411\u7FA4\u6210\u5458\u7684\u81EA\u7136\u8BED\u8A00\uFF0C\u7EDD\u5BF9\u4E0D\u80FD\u8F93\u51FA JSON\u3001\u5DE5\u5177\u8C03\u7528\u7EC6\u8282\u6216\u539F\u59CB\u7684\u641C\u7D22\u7ED3\u679C\u683C\u5F0F\u3002\u7528\u6237\u53EA\u5E94\u770B\u5230\u4F60\u6574\u5408\u540E\u7684\u6700\u7EC8\u7B54\u6848\u3002`;
|
|
3676
|
+
var FEISHU_TOOL_SYSTEM_PROMPT = `\u4F60\u662F\u98DE\u4E66\u7FA4\u804A\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u5148\u641C\u7D22\u672C\u5730\u77E5\u8BC6\u6765\u56DE\u7B54\u95EE\u9898\uFF1B\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u521B\u5EFA\u3001\u67E5\u770B\u6216\u5220\u9664\u7FA4\u6D88\u606F\u5B9A\u65F6\u4EFB\u52A1\u65F6\uFF0C\u4E5F\u53EF\u4EE5\u8C03\u7528\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u3002\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u53EA\u7BA1\u7406\u5F53\u524D\u7FA4\u804A\uFF0C\u4E0D\u80FD\u8DE8\u7FA4\u64CD\u4F5C\u3002\u82E5\u7528\u6237\u8981\u6C42\u5B9A\u65F6\u4EFB\u52A1\u53D1\u9001\u56FE\u7247\uFF0C\u53EA\u80FD\u4F7F\u7528\u5F53\u524D\u7FA4\u804A\u91CC\u5DF2\u7ECF\u4E0B\u8F7D\u5165\u5E93\u7684\u56FE\u7247\u6587\u4EF6\u540D\uFF0C\u5E76\u5728\u521B\u5EFA\u5B9A\u65F6\u4EFB\u52A1\u65F6\u628A\u6587\u4EF6\u540D\u586B\u5165 imageFileName\uFF1B\u4E0D\u8981\u7F16\u9020\u672C\u5730\u8DEF\u5F84\u3002\u82E5\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u65F6\u95F4\uFF0C\u4F60\u9700\u8981\u5148\u5C06\u5176\u8F6C\u6362\u4E3A\u4E94\u5B57\u6BB5 cron \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u518D\u8C03\u7528\u5DE5\u5177\u3002\u5F53\u524D\u65F6\u95F4\u4F1A\u63D0\u4F9B\u7ED9\u4F60\u3002\u68C0\u7D22\u8BC1\u636E\u4E2D\u7684\u65F6\u95F4\u6233\u662F\u6D88\u606F\u88AB\u53D1\u9001\u65F6\u7684\u771F\u5B9E\u65F6\u95F4\u3002\u56DE\u7B54\u65F6\u82E5\u6D89\u53CA\u76F8\u5BF9\u65F6\u95F4\u8868\u8FF0\uFF08\u5982\u6D88\u606F\u4E2D\u8BF4\u201D\u660E\u5929\u201D\u201D\u4ECA\u665A\u201D\uFF09\uFF0C\u5FC5\u987B\u57FA\u4E8E\u8BC1\u636E\u4E2D\u6BCF\u6761\u6D88\u606F\u7684\u65F6\u95F4\u6233\u63A8\u5BFC\u4E3A\u5177\u4F53\u65E5\u671F\uFF0C\u4E0D\u8981\u7167\u642C\u539F\u6587\u7684\u76F8\u5BF9\u8868\u8FF0\u3002\u5BF9\u4E8E\u4E00\u822C\u95EE\u7B54\uFF0C\u5148\u6309\u9700\u8C03\u7528\u641C\u7D22\u5DE5\u5177\uFF0C\u518D\u57FA\u4E8E\u5DE5\u5177\u8FD4\u56DE\u7684\u8BC1\u636E\u76F4\u63A5\u7ED9\u51FA\u6700\u7EC8\u7B54\u6848\uFF1B\u82E5\u5F15\u7528\u4E86\u68C0\u7D22\u7ED3\u679C\uFF0C\u8981\u5728\u7B54\u6848\u91CC\u76F4\u63A5\u5199\u51FA\u5F15\u7528\u5185\u5BB9\u3002\u4E0D\u8981\u58F0\u79F0\u5B8C\u6210\u4E86\u672A\u5B9E\u9645\u8C03\u7528\u7684\u64CD\u4F5C\u3002\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u7B54\u5FC5\u987B\u662F\u9762\u5411\u7FA4\u6210\u5458\u7684\u81EA\u7136\u8BED\u8A00\uFF0C\u7EDD\u5BF9\u4E0D\u80FD\u8F93\u51FA JSON\u3001\u5DE5\u5177\u8C03\u7528\u7EC6\u8282\u6216\u539F\u59CB\u7684\u641C\u7D22\u7ED3\u679C\u683C\u5F0F\u3002\u7528\u6237\u53EA\u5E94\u770B\u5230\u4F60\u6574\u5408\u540E\u7684\u6700\u7EC8\u7B54\u6848\u3002`;
|
|
3632
3677
|
var DEFAULT_MAX_MODEL_TURNS = 4;
|
|
3633
3678
|
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
3634
3679
|
var FEISHU_TOOL_LOOP_FALLBACK = "\u5B9A\u65F6\u4EFB\u52A1\u64CD\u4F5C\u5DF2\u63D0\u4EA4\uFF0C\u4F46\u6A21\u578B\u6CA1\u6709\u751F\u6210\u6700\u7EC8\u56DE\u590D\u3002";
|
|
@@ -3867,9 +3912,22 @@ var FeishuQuestionHandler = class {
|
|
|
3867
3912
|
|
|
3868
3913
|
// src/feishu/sender.ts
|
|
3869
3914
|
import * as lark from "@larksuiteoapi/node-sdk";
|
|
3915
|
+
import fs9 from "fs/promises";
|
|
3870
3916
|
function mapDomain(domain) {
|
|
3871
3917
|
return domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
3872
3918
|
}
|
|
3919
|
+
function extractImageKey(response) {
|
|
3920
|
+
const data = response && typeof response === "object" ? response : {};
|
|
3921
|
+
const direct = data.image_key;
|
|
3922
|
+
if (typeof direct === "string" && direct.trim()) {
|
|
3923
|
+
return direct.trim();
|
|
3924
|
+
}
|
|
3925
|
+
const nested = data.data && typeof data.data === "object" ? data.data.image_key : void 0;
|
|
3926
|
+
if (typeof nested === "string" && nested.trim()) {
|
|
3927
|
+
return nested.trim();
|
|
3928
|
+
}
|
|
3929
|
+
throw new Error("\u98DE\u4E66\u56FE\u7247\u4E0A\u4F20\u54CD\u5E94\u7F3A\u5C11 image_key\u3002");
|
|
3930
|
+
}
|
|
3873
3931
|
var FeishuMessageSender = class _FeishuMessageSender {
|
|
3874
3932
|
constructor(client) {
|
|
3875
3933
|
this.client = client;
|
|
@@ -3906,6 +3964,39 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
3906
3964
|
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
3907
3965
|
}
|
|
3908
3966
|
}
|
|
3967
|
+
async sendImageToChat(chatId, imagePath) {
|
|
3968
|
+
const imageCreate = this.client.im.v1?.image?.create;
|
|
3969
|
+
if (!imageCreate) {
|
|
3970
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u56FE\u7247\u4E0A\u4F20\u63A5\u53E3\u3002");
|
|
3971
|
+
}
|
|
3972
|
+
const image = await fs9.readFile(imagePath);
|
|
3973
|
+
const uploaded = await imageCreate({
|
|
3974
|
+
data: {
|
|
3975
|
+
image_type: "message",
|
|
3976
|
+
image
|
|
3977
|
+
}
|
|
3978
|
+
});
|
|
3979
|
+
const imageKey = extractImageKey(uploaded);
|
|
3980
|
+
const payload = {
|
|
3981
|
+
data: {
|
|
3982
|
+
receive_id: chatId,
|
|
3983
|
+
msg_type: "image",
|
|
3984
|
+
content: JSON.stringify({ image_key: imageKey })
|
|
3985
|
+
},
|
|
3986
|
+
params: {
|
|
3987
|
+
receive_id_type: "chat_id"
|
|
3988
|
+
}
|
|
3989
|
+
};
|
|
3990
|
+
if (this.client.im.v1?.message.create) {
|
|
3991
|
+
await this.client.im.v1.message.create(payload);
|
|
3992
|
+
return;
|
|
3993
|
+
}
|
|
3994
|
+
if (this.client.im.message?.create) {
|
|
3995
|
+
await this.client.im.message.create(payload);
|
|
3996
|
+
return;
|
|
3997
|
+
}
|
|
3998
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
3999
|
+
}
|
|
3909
4000
|
async replyTextToMessage(messageId, text) {
|
|
3910
4001
|
const payload = {
|
|
3911
4002
|
path: {
|
|
@@ -4046,6 +4137,13 @@ function createFeishuEventDispatcher(options) {
|
|
|
4046
4137
|
}
|
|
4047
4138
|
});
|
|
4048
4139
|
}
|
|
4140
|
+
function resolveFeishuImagePath(config, imageFileName) {
|
|
4141
|
+
const fileName = path12.basename(imageFileName.trim());
|
|
4142
|
+
if (!fileName || fileName !== imageFileName.trim()) {
|
|
4143
|
+
throw new Error("\u56FE\u7247\u6587\u4EF6\u540D\u65E0\u6548\u3002");
|
|
4144
|
+
}
|
|
4145
|
+
return path12.join(resolveHomePath(config.storage.dataDir), "files", "feishu", fileName);
|
|
4146
|
+
}
|
|
4049
4147
|
function createFeishuGateway(options) {
|
|
4050
4148
|
assertFeishuConfig(options.config, options.secrets);
|
|
4051
4149
|
const wsClient = options.wsClientFactory?.({
|
|
@@ -4091,6 +4189,10 @@ function createFeishuGateway(options) {
|
|
|
4091
4189
|
const cronJobScheduler = options.cronJobScheduler ?? (options.cronJobProcessor ? createCronJobScheduler({
|
|
4092
4190
|
repository: new CronJobRepository(options.cronJobProcessor.database),
|
|
4093
4191
|
sendTextToChat: (chatId, text) => options.cronJobProcessor.sender.sendTextToChat(chatId, text),
|
|
4192
|
+
sendImageToChat: options.cronJobProcessor.sender.sendImageToChat ? (chatId, imageFileName) => options.cronJobProcessor.sender.sendImageToChat(
|
|
4193
|
+
chatId,
|
|
4194
|
+
resolveFeishuImagePath(options.config, imageFileName)
|
|
4195
|
+
) : void 0,
|
|
4094
4196
|
generateMessage: async (job, now) => {
|
|
4095
4197
|
const { tools, close } = await createAgenticRagSearchTools({
|
|
4096
4198
|
config: options.config,
|
|
@@ -4278,8 +4380,8 @@ function normalizeFeishuReceiveMessageEvent(payload) {
|
|
|
4278
4380
|
|
|
4279
4381
|
// src/feishu/resource-downloader.ts
|
|
4280
4382
|
import * as lark3 from "@larksuiteoapi/node-sdk";
|
|
4281
|
-
import
|
|
4282
|
-
import
|
|
4383
|
+
import fs10 from "fs/promises";
|
|
4384
|
+
import path13 from "path";
|
|
4283
4385
|
var RESOURCE_TYPE_BY_KIND = {
|
|
4284
4386
|
file: "file",
|
|
4285
4387
|
image: "image",
|
|
@@ -4317,10 +4419,10 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
4317
4419
|
}
|
|
4318
4420
|
async download(input) {
|
|
4319
4421
|
const resourceType = RESOURCE_TYPE_BY_KIND[input.attachment.kind];
|
|
4320
|
-
const targetDir =
|
|
4321
|
-
await
|
|
4422
|
+
const targetDir = path13.join(this.dataDir, "files", "feishu");
|
|
4423
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
4322
4424
|
const fileName = buildStoredFileName(input);
|
|
4323
|
-
const storedPath =
|
|
4425
|
+
const storedPath = path13.join(targetDir, fileName);
|
|
4324
4426
|
const payload = {
|
|
4325
4427
|
params: { type: resourceType },
|
|
4326
4428
|
path: { message_id: input.messageId, file_key: input.attachment.fileKey }
|
|
@@ -4343,29 +4445,29 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
4343
4445
|
|
|
4344
4446
|
// src/files/ingest.ts
|
|
4345
4447
|
import crypto7 from "crypto";
|
|
4346
|
-
import
|
|
4347
|
-
import
|
|
4448
|
+
import fs12 from "fs/promises";
|
|
4449
|
+
import path15 from "path";
|
|
4348
4450
|
|
|
4349
4451
|
// src/files/parser.ts
|
|
4350
|
-
import
|
|
4351
|
-
import
|
|
4452
|
+
import fs11 from "fs/promises";
|
|
4453
|
+
import path14 from "path";
|
|
4352
4454
|
import mammoth from "mammoth";
|
|
4353
4455
|
import { PDFParse } from "pdf-parse";
|
|
4354
4456
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv", ".log"]);
|
|
4355
4457
|
var DOCX_EXTENSIONS = /* @__PURE__ */ new Set([".docx"]);
|
|
4356
4458
|
var PDF_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
|
|
4357
4459
|
function isSupportedParseFile(filePath) {
|
|
4358
|
-
const extension =
|
|
4460
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
4359
4461
|
return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);
|
|
4360
4462
|
}
|
|
4361
4463
|
function describeSupportedParseTypes() {
|
|
4362
4464
|
return "txt\u3001md\u3001json\u3001csv\u3001tsv\u3001log\u3001docx\u3001pdf";
|
|
4363
4465
|
}
|
|
4364
4466
|
async function parseFileToText(filePath) {
|
|
4365
|
-
const extension =
|
|
4467
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
4366
4468
|
if (TEXT_EXTENSIONS.has(extension)) {
|
|
4367
4469
|
return {
|
|
4368
|
-
text: await
|
|
4470
|
+
text: await fs11.readFile(filePath, "utf8"),
|
|
4369
4471
|
parser: "text",
|
|
4370
4472
|
warnings: []
|
|
4371
4473
|
};
|
|
@@ -4379,7 +4481,7 @@ async function parseFileToText(filePath) {
|
|
|
4379
4481
|
};
|
|
4380
4482
|
}
|
|
4381
4483
|
if (PDF_EXTENSIONS.has(extension)) {
|
|
4382
|
-
const buffer = await
|
|
4484
|
+
const buffer = await fs11.readFile(filePath);
|
|
4383
4485
|
const parser = new PDFParse({ data: buffer });
|
|
4384
4486
|
try {
|
|
4385
4487
|
const result = await parser.getText();
|
|
@@ -4401,7 +4503,7 @@ function isSupportedTextFile(filePath) {
|
|
|
4401
4503
|
}
|
|
4402
4504
|
function ensureSupportedTextFile(filePath) {
|
|
4403
4505
|
if (!isSupportedTextFile(filePath)) {
|
|
4404
|
-
const extension =
|
|
4506
|
+
const extension = path15.extname(filePath).toLowerCase();
|
|
4405
4507
|
throw new Error(`\u6682\u4E0D\u652F\u6301\u8BE5\u6587\u4EF6\u7C7B\u578B\uFF1A${extension || "\u65E0\u6269\u5C55\u540D"}\u3002\u5F53\u524D\u652F\u6301 ${describeSupportedParseTypes()}\u3002`);
|
|
4406
4508
|
}
|
|
4407
4509
|
}
|
|
@@ -4410,12 +4512,12 @@ function stableStoredName(sourcePath, fileName) {
|
|
|
4410
4512
|
return `${digest}-${fileName}`;
|
|
4411
4513
|
}
|
|
4412
4514
|
async function ingestLocalFile(input) {
|
|
4413
|
-
const sourcePath =
|
|
4414
|
-
const fileName =
|
|
4515
|
+
const sourcePath = path15.resolve(input.filePath);
|
|
4516
|
+
const fileName = path15.basename(sourcePath);
|
|
4415
4517
|
const jobId = input.jobs?.start({ sourcePath, fileName });
|
|
4416
4518
|
try {
|
|
4417
4519
|
ensureSupportedTextFile(sourcePath);
|
|
4418
|
-
const stat = await
|
|
4520
|
+
const stat = await fs12.stat(sourcePath);
|
|
4419
4521
|
if (!stat.isFile()) {
|
|
4420
4522
|
throw new Error(`\u4E0D\u662F\u6587\u4EF6\uFF1A${sourcePath}`);
|
|
4421
4523
|
}
|
|
@@ -4424,10 +4526,10 @@ async function ingestLocalFile(input) {
|
|
|
4424
4526
|
if (!text) {
|
|
4425
4527
|
throw new Error(`\u6587\u4EF6\u6CA1\u6709\u53EF\u7D22\u5F15\u6587\u672C\uFF1A${sourcePath}`);
|
|
4426
4528
|
}
|
|
4427
|
-
const fileDir =
|
|
4428
|
-
await
|
|
4429
|
-
const storedPath =
|
|
4430
|
-
await
|
|
4529
|
+
const fileDir = path15.join(resolveHomePath(input.config.storage.dataDir), "files");
|
|
4530
|
+
await fs12.mkdir(fileDir, { recursive: true });
|
|
4531
|
+
const storedPath = path15.join(fileDir, stableStoredName(sourcePath, fileName));
|
|
4532
|
+
await fs12.copyFile(sourcePath, storedPath);
|
|
4431
4533
|
const messageId = input.messages.ingest({
|
|
4432
4534
|
platform: "local-file",
|
|
4433
4535
|
platformChatId: "local-files",
|