chattercatcher 0.1.25 → 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 +163 -43
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +150 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { input, password, select, confirm, number } from "@inquirer/prompts";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import
|
|
6
|
+
import fs15 from "fs/promises";
|
|
7
7
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "chattercatcher",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.27",
|
|
12
12
|
description: "\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u5E93\u673A\u5668\u4EBA",
|
|
13
13
|
type: "module",
|
|
14
14
|
main: "dist/index.js",
|
|
@@ -548,12 +548,17 @@ function migrateDatabase(database) {
|
|
|
548
548
|
next_run_at TEXT NOT NULL,
|
|
549
549
|
last_error TEXT,
|
|
550
550
|
created_at TEXT NOT NULL,
|
|
551
|
-
updated_at TEXT NOT NULL
|
|
551
|
+
updated_at TEXT NOT NULL,
|
|
552
|
+
image_file_name TEXT
|
|
552
553
|
);
|
|
553
554
|
|
|
554
555
|
CREATE INDEX IF NOT EXISTS cron_jobs_chat_status_idx ON cron_jobs(chat_id, status, updated_at);
|
|
555
556
|
CREATE INDEX IF NOT EXISTS cron_jobs_due_idx ON cron_jobs(status, next_run_at);
|
|
556
557
|
`);
|
|
558
|
+
const cronJobColumns = database.prepare("PRAGMA table_info(cron_jobs)").all();
|
|
559
|
+
if (!cronJobColumns.some((column) => column.name === "image_file_name")) {
|
|
560
|
+
database.prepare("ALTER TABLE cron_jobs ADD COLUMN image_file_name TEXT").run();
|
|
561
|
+
}
|
|
557
562
|
}
|
|
558
563
|
|
|
559
564
|
// src/doctor/checks.ts
|
|
@@ -1304,6 +1309,9 @@ var MessageRepository = class {
|
|
|
1304
1309
|
throw new Error("\u539F\u59CB\u56FE\u7247\u6D88\u606F\u4E0D\u5B58\u5728\u3002");
|
|
1305
1310
|
}
|
|
1306
1311
|
const derivedPlatformMessageId = `${source.platformMessageId}:image-summary:${input2.imageKey}`;
|
|
1312
|
+
const imageFileName = input2.imageFileName?.trim();
|
|
1313
|
+
const summaryText = imageFileName ? `[\u56FE\u7247\u8F6C\u8FF0] \u6587\u4EF6\u540D\uFF1A${imageFileName}
|
|
1314
|
+
${input2.summary.trim()}` : `[\u56FE\u7247\u8F6C\u8FF0] ${input2.summary.trim()}`;
|
|
1307
1315
|
return this.ingest({
|
|
1308
1316
|
platform: source.platform,
|
|
1309
1317
|
platformChatId: source.platformChatId,
|
|
@@ -1312,12 +1320,13 @@ var MessageRepository = class {
|
|
|
1312
1320
|
senderId: source.senderId,
|
|
1313
1321
|
senderName: source.senderName,
|
|
1314
1322
|
messageType: "image_summary",
|
|
1315
|
-
text:
|
|
1323
|
+
text: summaryText,
|
|
1316
1324
|
sentAt: source.sentAt,
|
|
1317
1325
|
rawPayload: {
|
|
1318
1326
|
derivedFromMessageId: input2.sourceMessageId,
|
|
1319
1327
|
sourceAttachmentKind: "image",
|
|
1320
1328
|
sourceResourceKey: input2.imageKey,
|
|
1329
|
+
...imageFileName ? { imageFileName } : {},
|
|
1321
1330
|
multimodalModel: input2.multimodalModel,
|
|
1322
1331
|
isMeaningful: true,
|
|
1323
1332
|
...input2.reason?.trim() ? { reason: input2.reason.trim() } : {},
|
|
@@ -2618,7 +2627,7 @@ async function summarizeEpisodeWindow(window, model, now) {
|
|
|
2618
2627
|
const summary = await model.complete([
|
|
2619
2628
|
{
|
|
2620
2629
|
role: "system",
|
|
2621
|
-
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"
|
|
2630
|
+
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"
|
|
2622
2631
|
},
|
|
2623
2632
|
{
|
|
2624
2633
|
role: "user",
|
|
@@ -2723,6 +2732,7 @@ async function ensureFeishuBotOpenId(config, secrets, options = {}) {
|
|
|
2723
2732
|
|
|
2724
2733
|
// src/feishu/gateway.ts
|
|
2725
2734
|
import * as lark2 from "@larksuiteoapi/node-sdk";
|
|
2735
|
+
import path12 from "path";
|
|
2726
2736
|
|
|
2727
2737
|
// src/cron/jobs.ts
|
|
2728
2738
|
import crypto4 from "crypto";
|
|
@@ -2827,6 +2837,7 @@ var CronJobRepository = class {
|
|
|
2827
2837
|
create(input2) {
|
|
2828
2838
|
const schedule = input2.schedule.trim();
|
|
2829
2839
|
const prompt = input2.prompt.trim();
|
|
2840
|
+
const imageFileName = input2.imageFileName?.trim();
|
|
2830
2841
|
if (!isValidCronSchedule(schedule)) {
|
|
2831
2842
|
throw new Error("cron \u8868\u8FBE\u5F0F\u65E0\u6548\u3002");
|
|
2832
2843
|
}
|
|
@@ -2844,6 +2855,7 @@ var CronJobRepository = class {
|
|
|
2844
2855
|
createdByOpenId: input2.createdByOpenId,
|
|
2845
2856
|
schedule,
|
|
2846
2857
|
prompt,
|
|
2858
|
+
...imageFileName ? { imageFileName } : {},
|
|
2847
2859
|
status: "active",
|
|
2848
2860
|
nextRunAt: nextRunAt.toISOString(),
|
|
2849
2861
|
createdAt: now.toISOString(),
|
|
@@ -2852,15 +2864,18 @@ var CronJobRepository = class {
|
|
|
2852
2864
|
this.database.prepare(
|
|
2853
2865
|
`
|
|
2854
2866
|
INSERT INTO cron_jobs (
|
|
2855
|
-
id, chat_id, created_by_open_id, schedule, prompt, status,
|
|
2867
|
+
id, chat_id, created_by_open_id, schedule, prompt, image_file_name, status,
|
|
2856
2868
|
last_run_at, next_run_at, last_error, created_at, updated_at
|
|
2857
2869
|
)
|
|
2858
2870
|
VALUES (
|
|
2859
|
-
@id, @chatId, @createdByOpenId, @schedule, @prompt, @status,
|
|
2871
|
+
@id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName, @status,
|
|
2860
2872
|
NULL, @nextRunAt, NULL, @createdAt, @updatedAt
|
|
2861
2873
|
)
|
|
2862
2874
|
`
|
|
2863
|
-
).run(
|
|
2875
|
+
).run({
|
|
2876
|
+
...record,
|
|
2877
|
+
imageFileName: record.imageFileName ?? null
|
|
2878
|
+
});
|
|
2864
2879
|
return record;
|
|
2865
2880
|
}
|
|
2866
2881
|
get(id) {
|
|
@@ -2885,6 +2900,7 @@ var CronJobRepository = class {
|
|
|
2885
2900
|
created_by_open_id AS createdByOpenId,
|
|
2886
2901
|
schedule,
|
|
2887
2902
|
prompt,
|
|
2903
|
+
image_file_name AS imageFileName,
|
|
2888
2904
|
status,
|
|
2889
2905
|
last_run_at AS lastRunAt,
|
|
2890
2906
|
next_run_at AS nextRunAt,
|
|
@@ -2903,6 +2919,7 @@ var CronJobRepository = class {
|
|
|
2903
2919
|
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
2904
2920
|
schedule: row.schedule,
|
|
2905
2921
|
prompt: row.prompt,
|
|
2922
|
+
imageFileName: row.imageFileName ?? void 0,
|
|
2906
2923
|
status: row.status,
|
|
2907
2924
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
2908
2925
|
nextRunAt: row.nextRunAt,
|
|
@@ -2976,6 +2993,7 @@ var CronJobRepository = class {
|
|
|
2976
2993
|
created_by_open_id AS createdByOpenId,
|
|
2977
2994
|
schedule,
|
|
2978
2995
|
prompt,
|
|
2996
|
+
image_file_name AS imageFileName,
|
|
2979
2997
|
status,
|
|
2980
2998
|
last_run_at AS lastRunAt,
|
|
2981
2999
|
next_run_at AS nextRunAt,
|
|
@@ -2994,6 +3012,7 @@ var CronJobRepository = class {
|
|
|
2994
3012
|
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
2995
3013
|
schedule: row.schedule,
|
|
2996
3014
|
prompt: row.prompt,
|
|
3015
|
+
imageFileName: row.imageFileName ?? void 0,
|
|
2997
3016
|
status: row.status,
|
|
2998
3017
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
2999
3018
|
nextRunAt: row.nextRunAt,
|
|
@@ -3098,6 +3117,12 @@ function createCronJobScheduler(options) {
|
|
|
3098
3117
|
try {
|
|
3099
3118
|
const text = await options.generateMessage(job, startedAt);
|
|
3100
3119
|
await options.sendTextToChat(job.chatId, text);
|
|
3120
|
+
if (job.imageFileName) {
|
|
3121
|
+
if (!options.sendImageToChat) {
|
|
3122
|
+
throw new Error("\u5F53\u524D\u5B9A\u65F6\u4EFB\u52A1\u8FD0\u884C\u73AF\u5883\u4E0D\u652F\u6301\u53D1\u9001\u56FE\u7247\u3002");
|
|
3123
|
+
}
|
|
3124
|
+
await options.sendImageToChat(job.chatId, job.imageFileName);
|
|
3125
|
+
}
|
|
3101
3126
|
options.repository.markSuccess(job.id, startedAt);
|
|
3102
3127
|
} catch (error) {
|
|
3103
3128
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -3514,6 +3539,7 @@ var ImageMultimodalTaskRepository = class {
|
|
|
3514
3539
|
};
|
|
3515
3540
|
|
|
3516
3541
|
// src/multimodal/worker.ts
|
|
3542
|
+
import path11 from "path";
|
|
3517
3543
|
var ImageMultimodalWorker = class {
|
|
3518
3544
|
constructor(options) {
|
|
3519
3545
|
this.options = options;
|
|
@@ -3540,9 +3566,11 @@ var ImageMultimodalWorker = class {
|
|
|
3540
3566
|
throw error;
|
|
3541
3567
|
}
|
|
3542
3568
|
try {
|
|
3569
|
+
const imageFileName = path11.basename(running.storedPath);
|
|
3543
3570
|
const described = await this.options.model.describeImage({
|
|
3544
3571
|
imagePath: running.storedPath,
|
|
3545
|
-
mimeType: running.mimeType
|
|
3572
|
+
mimeType: running.mimeType,
|
|
3573
|
+
context: `\u56FE\u7247\u6587\u4EF6\u540D\uFF1A${imageFileName}`
|
|
3546
3574
|
});
|
|
3547
3575
|
if (!described.isMeaningful) {
|
|
3548
3576
|
this.options.tasks.markSkipped(running.id, described.reason || "\u591A\u6A21\u6001\u6A21\u578B\u5224\u5B9A\u56FE\u7247\u65E0\u610F\u4E49\u3002");
|
|
@@ -3552,6 +3580,7 @@ var ImageMultimodalWorker = class {
|
|
|
3552
3580
|
const derivedMessageId = this.options.messages.createImageSummaryMessage({
|
|
3553
3581
|
sourceMessageId: running.sourceMessageId,
|
|
3554
3582
|
imageKey: running.imageKey,
|
|
3583
|
+
imageFileName,
|
|
3555
3584
|
summary: described.summary,
|
|
3556
3585
|
reason: described.reason,
|
|
3557
3586
|
multimodalModel: this.options.multimodalModelName,
|
|
@@ -3585,6 +3614,17 @@ function readString(input2, key) {
|
|
|
3585
3614
|
}
|
|
3586
3615
|
return value.trim();
|
|
3587
3616
|
}
|
|
3617
|
+
function readOptionalString(input2, key) {
|
|
3618
|
+
const value = typeof input2 === "object" && input2 !== null && key in input2 ? input2[key] : void 0;
|
|
3619
|
+
if (value === void 0 || value === null) {
|
|
3620
|
+
return void 0;
|
|
3621
|
+
}
|
|
3622
|
+
if (typeof value !== "string") {
|
|
3623
|
+
throw new Error(`${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002`);
|
|
3624
|
+
}
|
|
3625
|
+
const trimmed = value.trim();
|
|
3626
|
+
return trimmed || void 0;
|
|
3627
|
+
}
|
|
3588
3628
|
function createCronJobTools(input2) {
|
|
3589
3629
|
return [
|
|
3590
3630
|
{
|
|
@@ -3600,6 +3640,10 @@ function createCronJobTools(input2) {
|
|
|
3600
3640
|
prompt: {
|
|
3601
3641
|
type: "string",
|
|
3602
3642
|
description: "Prompt used later to generate the scheduled message."
|
|
3643
|
+
},
|
|
3644
|
+
imageFileName: {
|
|
3645
|
+
type: "string",
|
|
3646
|
+
description: "Optional image filename already stored from the current chat, for example om_xxx-image.jpg."
|
|
3603
3647
|
}
|
|
3604
3648
|
},
|
|
3605
3649
|
required: ["schedule", "prompt"],
|
|
@@ -3610,7 +3654,8 @@ function createCronJobTools(input2) {
|
|
|
3610
3654
|
chatId: input2.chatId,
|
|
3611
3655
|
createdByOpenId: input2.createdByOpenId,
|
|
3612
3656
|
schedule: readString(rawInput, "schedule"),
|
|
3613
|
-
prompt: readString(rawInput, "prompt")
|
|
3657
|
+
prompt: readString(rawInput, "prompt"),
|
|
3658
|
+
imageFileName: readOptionalString(rawInput, "imageFileName")
|
|
3614
3659
|
});
|
|
3615
3660
|
return JSON.stringify({ ok: true, job });
|
|
3616
3661
|
}
|
|
@@ -3773,19 +3818,36 @@ function stripMentions(text, mentions) {
|
|
|
3773
3818
|
}
|
|
3774
3819
|
return result.replace(/@/g, " ").replace(/\s+/g, " ").trim();
|
|
3775
3820
|
}
|
|
3776
|
-
var FEISHU_TOOL_SYSTEM_PROMPT =
|
|
3821
|
+
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`;
|
|
3777
3822
|
var DEFAULT_MAX_MODEL_TURNS = 4;
|
|
3778
3823
|
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
3779
3824
|
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";
|
|
3780
3825
|
var FEISHU_TOOL_LOOP_LIMIT_REACHED = "\u5DE5\u5177\u8C03\u7528\u6B21\u6570\u5DF2\u8FBE\u5230\u4E0A\u9650\uFF0C\u8BF7\u7F29\u5C0F\u8BF7\u6C42\u540E\u91CD\u8BD5\u3002";
|
|
3781
3826
|
function toToolResultContent(value) {
|
|
3782
|
-
|
|
3827
|
+
if (typeof value === "string") return value;
|
|
3828
|
+
return JSON.stringify(value);
|
|
3829
|
+
}
|
|
3830
|
+
function isEvidenceBlockArray(value) {
|
|
3831
|
+
return Array.isArray(value) && value.length > 0 && typeof value[0]?.text === "string";
|
|
3832
|
+
}
|
|
3833
|
+
function formatEvidenceBlocks(blocks) {
|
|
3834
|
+
return blocks.map((block, index2) => {
|
|
3835
|
+
const source = block.source;
|
|
3836
|
+
const sender = source.sender ? `${source.sender} ` : "";
|
|
3837
|
+
const timestamp = source.timestamp ? `(${source.timestamp.slice(0, 19).replace("T", " ")})` : "";
|
|
3838
|
+
const header = `[\u8BC1\u636E${index2 + 1}] ${sender}${timestamp}:`;
|
|
3839
|
+
return `${header}
|
|
3840
|
+
${block.text}`;
|
|
3841
|
+
}).join("\n\n");
|
|
3783
3842
|
}
|
|
3784
3843
|
function toToolErrorContent(message) {
|
|
3785
3844
|
return JSON.stringify({ ok: false, error: message });
|
|
3786
3845
|
}
|
|
3787
3846
|
async function executeFeishuTool(tool, input2) {
|
|
3788
3847
|
const result = await tool.execute(input2);
|
|
3848
|
+
if (isEvidenceBlockArray(result)) {
|
|
3849
|
+
return formatEvidenceBlocks(result);
|
|
3850
|
+
}
|
|
3789
3851
|
return toToolResultContent(result);
|
|
3790
3852
|
}
|
|
3791
3853
|
async function runFeishuToolLoop(input2) {
|
|
@@ -3995,9 +4057,22 @@ var FeishuQuestionHandler = class {
|
|
|
3995
4057
|
|
|
3996
4058
|
// src/feishu/sender.ts
|
|
3997
4059
|
import * as lark from "@larksuiteoapi/node-sdk";
|
|
4060
|
+
import fs9 from "fs/promises";
|
|
3998
4061
|
function mapDomain(domain) {
|
|
3999
4062
|
return domain === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
4000
4063
|
}
|
|
4064
|
+
function extractImageKey(response) {
|
|
4065
|
+
const data2 = response && typeof response === "object" ? response : {};
|
|
4066
|
+
const direct = data2.image_key;
|
|
4067
|
+
if (typeof direct === "string" && direct.trim()) {
|
|
4068
|
+
return direct.trim();
|
|
4069
|
+
}
|
|
4070
|
+
const nested = data2.data && typeof data2.data === "object" ? data2.data.image_key : void 0;
|
|
4071
|
+
if (typeof nested === "string" && nested.trim()) {
|
|
4072
|
+
return nested.trim();
|
|
4073
|
+
}
|
|
4074
|
+
throw new Error("\u98DE\u4E66\u56FE\u7247\u4E0A\u4F20\u54CD\u5E94\u7F3A\u5C11 image_key\u3002");
|
|
4075
|
+
}
|
|
4001
4076
|
var FeishuMessageSender = class _FeishuMessageSender {
|
|
4002
4077
|
constructor(client) {
|
|
4003
4078
|
this.client = client;
|
|
@@ -4034,6 +4109,39 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4034
4109
|
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4035
4110
|
}
|
|
4036
4111
|
}
|
|
4112
|
+
async sendImageToChat(chatId, imagePath) {
|
|
4113
|
+
const imageCreate = this.client.im.v1?.image?.create;
|
|
4114
|
+
if (!imageCreate) {
|
|
4115
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u56FE\u7247\u4E0A\u4F20\u63A5\u53E3\u3002");
|
|
4116
|
+
}
|
|
4117
|
+
const image = await fs9.readFile(imagePath);
|
|
4118
|
+
const uploaded = await imageCreate({
|
|
4119
|
+
data: {
|
|
4120
|
+
image_type: "message",
|
|
4121
|
+
image
|
|
4122
|
+
}
|
|
4123
|
+
});
|
|
4124
|
+
const imageKey = extractImageKey(uploaded);
|
|
4125
|
+
const payload = {
|
|
4126
|
+
data: {
|
|
4127
|
+
receive_id: chatId,
|
|
4128
|
+
msg_type: "image",
|
|
4129
|
+
content: JSON.stringify({ image_key: imageKey })
|
|
4130
|
+
},
|
|
4131
|
+
params: {
|
|
4132
|
+
receive_id_type: "chat_id"
|
|
4133
|
+
}
|
|
4134
|
+
};
|
|
4135
|
+
if (this.client.im.v1?.message.create) {
|
|
4136
|
+
await this.client.im.v1.message.create(payload);
|
|
4137
|
+
return;
|
|
4138
|
+
}
|
|
4139
|
+
if (this.client.im.message?.create) {
|
|
4140
|
+
await this.client.im.message.create(payload);
|
|
4141
|
+
return;
|
|
4142
|
+
}
|
|
4143
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301\u6D88\u606F\u53D1\u9001\u63A5\u53E3\u3002");
|
|
4144
|
+
}
|
|
4037
4145
|
async replyTextToMessage(messageId, text) {
|
|
4038
4146
|
const payload = {
|
|
4039
4147
|
path: {
|
|
@@ -4174,6 +4282,13 @@ function createFeishuEventDispatcher(options) {
|
|
|
4174
4282
|
}
|
|
4175
4283
|
});
|
|
4176
4284
|
}
|
|
4285
|
+
function resolveFeishuImagePath(config, imageFileName) {
|
|
4286
|
+
const fileName = path12.basename(imageFileName.trim());
|
|
4287
|
+
if (!fileName || fileName !== imageFileName.trim()) {
|
|
4288
|
+
throw new Error("\u56FE\u7247\u6587\u4EF6\u540D\u65E0\u6548\u3002");
|
|
4289
|
+
}
|
|
4290
|
+
return path12.join(resolveHomePath(config.storage.dataDir), "files", "feishu", fileName);
|
|
4291
|
+
}
|
|
4177
4292
|
function createFeishuGateway(options) {
|
|
4178
4293
|
assertFeishuConfig(options.config, options.secrets);
|
|
4179
4294
|
const wsClient = options.wsClientFactory?.({
|
|
@@ -4219,6 +4334,10 @@ function createFeishuGateway(options) {
|
|
|
4219
4334
|
const cronJobScheduler = options.cronJobScheduler ?? (options.cronJobProcessor ? createCronJobScheduler({
|
|
4220
4335
|
repository: new CronJobRepository(options.cronJobProcessor.database),
|
|
4221
4336
|
sendTextToChat: (chatId, text) => options.cronJobProcessor.sender.sendTextToChat(chatId, text),
|
|
4337
|
+
sendImageToChat: options.cronJobProcessor.sender.sendImageToChat ? (chatId, imageFileName) => options.cronJobProcessor.sender.sendImageToChat(
|
|
4338
|
+
chatId,
|
|
4339
|
+
resolveFeishuImagePath(options.config, imageFileName)
|
|
4340
|
+
) : void 0,
|
|
4222
4341
|
generateMessage: async (job, now) => {
|
|
4223
4342
|
const { tools, close } = await createAgenticRagSearchTools({
|
|
4224
4343
|
config: options.config,
|
|
@@ -4256,8 +4375,8 @@ function createFeishuGateway(options) {
|
|
|
4256
4375
|
|
|
4257
4376
|
// src/feishu/resource-downloader.ts
|
|
4258
4377
|
import * as lark3 from "@larksuiteoapi/node-sdk";
|
|
4259
|
-
import
|
|
4260
|
-
import
|
|
4378
|
+
import fs10 from "fs/promises";
|
|
4379
|
+
import path13 from "path";
|
|
4261
4380
|
var RESOURCE_TYPE_BY_KIND = {
|
|
4262
4381
|
file: "file",
|
|
4263
4382
|
image: "image",
|
|
@@ -4295,10 +4414,10 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
4295
4414
|
}
|
|
4296
4415
|
async download(input2) {
|
|
4297
4416
|
const resourceType = RESOURCE_TYPE_BY_KIND[input2.attachment.kind];
|
|
4298
|
-
const targetDir =
|
|
4299
|
-
await
|
|
4417
|
+
const targetDir = path13.join(this.dataDir, "files", "feishu");
|
|
4418
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
4300
4419
|
const fileName = buildStoredFileName(input2);
|
|
4301
|
-
const storedPath =
|
|
4420
|
+
const storedPath = path13.join(targetDir, fileName);
|
|
4302
4421
|
const payload = {
|
|
4303
4422
|
params: { type: resourceType },
|
|
4304
4423
|
path: { message_id: input2.messageId, file_key: input2.attachment.fileKey }
|
|
@@ -4321,29 +4440,29 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
4321
4440
|
|
|
4322
4441
|
// src/files/ingest.ts
|
|
4323
4442
|
import crypto7 from "crypto";
|
|
4324
|
-
import
|
|
4325
|
-
import
|
|
4443
|
+
import fs12 from "fs/promises";
|
|
4444
|
+
import path15 from "path";
|
|
4326
4445
|
|
|
4327
4446
|
// src/files/parser.ts
|
|
4328
|
-
import
|
|
4329
|
-
import
|
|
4447
|
+
import fs11 from "fs/promises";
|
|
4448
|
+
import path14 from "path";
|
|
4330
4449
|
import mammoth from "mammoth";
|
|
4331
4450
|
import { PDFParse } from "pdf-parse";
|
|
4332
4451
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv", ".log"]);
|
|
4333
4452
|
var DOCX_EXTENSIONS = /* @__PURE__ */ new Set([".docx"]);
|
|
4334
4453
|
var PDF_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
|
|
4335
4454
|
function isSupportedParseFile(filePath) {
|
|
4336
|
-
const extension =
|
|
4455
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
4337
4456
|
return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);
|
|
4338
4457
|
}
|
|
4339
4458
|
function describeSupportedParseTypes() {
|
|
4340
4459
|
return "txt\u3001md\u3001json\u3001csv\u3001tsv\u3001log\u3001docx\u3001pdf";
|
|
4341
4460
|
}
|
|
4342
4461
|
async function parseFileToText(filePath) {
|
|
4343
|
-
const extension =
|
|
4462
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
4344
4463
|
if (TEXT_EXTENSIONS.has(extension)) {
|
|
4345
4464
|
return {
|
|
4346
|
-
text: await
|
|
4465
|
+
text: await fs11.readFile(filePath, "utf8"),
|
|
4347
4466
|
parser: "text",
|
|
4348
4467
|
warnings: []
|
|
4349
4468
|
};
|
|
@@ -4357,7 +4476,7 @@ async function parseFileToText(filePath) {
|
|
|
4357
4476
|
};
|
|
4358
4477
|
}
|
|
4359
4478
|
if (PDF_EXTENSIONS.has(extension)) {
|
|
4360
|
-
const buffer = await
|
|
4479
|
+
const buffer = await fs11.readFile(filePath);
|
|
4361
4480
|
const parser = new PDFParse({ data: buffer });
|
|
4362
4481
|
try {
|
|
4363
4482
|
const result = await parser.getText();
|
|
@@ -4379,7 +4498,7 @@ function isSupportedTextFile(filePath) {
|
|
|
4379
4498
|
}
|
|
4380
4499
|
function ensureSupportedTextFile(filePath) {
|
|
4381
4500
|
if (!isSupportedTextFile(filePath)) {
|
|
4382
|
-
const extension =
|
|
4501
|
+
const extension = path15.extname(filePath).toLowerCase();
|
|
4383
4502
|
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`);
|
|
4384
4503
|
}
|
|
4385
4504
|
}
|
|
@@ -4388,12 +4507,12 @@ function stableStoredName(sourcePath, fileName) {
|
|
|
4388
4507
|
return `${digest}-${fileName}`;
|
|
4389
4508
|
}
|
|
4390
4509
|
async function ingestLocalFile(input2) {
|
|
4391
|
-
const sourcePath =
|
|
4392
|
-
const fileName =
|
|
4510
|
+
const sourcePath = path15.resolve(input2.filePath);
|
|
4511
|
+
const fileName = path15.basename(sourcePath);
|
|
4393
4512
|
const jobId = input2.jobs?.start({ sourcePath, fileName });
|
|
4394
4513
|
try {
|
|
4395
4514
|
ensureSupportedTextFile(sourcePath);
|
|
4396
|
-
const stat = await
|
|
4515
|
+
const stat = await fs12.stat(sourcePath);
|
|
4397
4516
|
if (!stat.isFile()) {
|
|
4398
4517
|
throw new Error(`\u4E0D\u662F\u6587\u4EF6\uFF1A${sourcePath}`);
|
|
4399
4518
|
}
|
|
@@ -4402,10 +4521,10 @@ async function ingestLocalFile(input2) {
|
|
|
4402
4521
|
if (!text) {
|
|
4403
4522
|
throw new Error(`\u6587\u4EF6\u6CA1\u6709\u53EF\u7D22\u5F15\u6587\u672C\uFF1A${sourcePath}`);
|
|
4404
4523
|
}
|
|
4405
|
-
const fileDir =
|
|
4406
|
-
await
|
|
4407
|
-
const storedPath =
|
|
4408
|
-
await
|
|
4524
|
+
const fileDir = path15.join(resolveHomePath(input2.config.storage.dataDir), "files");
|
|
4525
|
+
await fs12.mkdir(fileDir, { recursive: true });
|
|
4526
|
+
const storedPath = path15.join(fileDir, stableStoredName(sourcePath, fileName));
|
|
4527
|
+
await fs12.copyFile(sourcePath, storedPath);
|
|
4409
4528
|
const messageId = input2.messages.ingest({
|
|
4410
4529
|
platform: "local-file",
|
|
4411
4530
|
platformChatId: "local-files",
|
|
@@ -4710,8 +4829,8 @@ var GatewayIngestor = class {
|
|
|
4710
4829
|
|
|
4711
4830
|
// src/gateway/detached.ts
|
|
4712
4831
|
import { spawn } from "child_process";
|
|
4713
|
-
import
|
|
4714
|
-
import
|
|
4832
|
+
import fs13 from "fs";
|
|
4833
|
+
import path16 from "path";
|
|
4715
4834
|
var START_FAILURE_GRACE_MS = 250;
|
|
4716
4835
|
function buildGatewayForegroundSpawnCommand(argv = process.argv) {
|
|
4717
4836
|
const [command = process.execPath, ...rawArgs] = argv;
|
|
@@ -4768,7 +4887,7 @@ async function startDetachedGateway(input2) {
|
|
|
4768
4887
|
...status.pid ? { pid: status.pid } : {}
|
|
4769
4888
|
};
|
|
4770
4889
|
}
|
|
4771
|
-
|
|
4890
|
+
fs13.mkdirSync(path16.dirname(logFile), { recursive: true });
|
|
4772
4891
|
let out;
|
|
4773
4892
|
let err;
|
|
4774
4893
|
let stdioClosed = false;
|
|
@@ -4778,15 +4897,15 @@ async function startDetachedGateway(input2) {
|
|
|
4778
4897
|
}
|
|
4779
4898
|
stdioClosed = true;
|
|
4780
4899
|
if (typeof out === "number") {
|
|
4781
|
-
|
|
4900
|
+
fs13.closeSync(out);
|
|
4782
4901
|
}
|
|
4783
4902
|
if (typeof err === "number") {
|
|
4784
|
-
|
|
4903
|
+
fs13.closeSync(err);
|
|
4785
4904
|
}
|
|
4786
4905
|
};
|
|
4787
4906
|
try {
|
|
4788
|
-
out =
|
|
4789
|
-
err =
|
|
4907
|
+
out = fs13.openSync(logFile, "a");
|
|
4908
|
+
err = fs13.openSync(logFile, "a");
|
|
4790
4909
|
const foreground = buildGatewayForegroundSpawnCommand(input2.argv);
|
|
4791
4910
|
const child = spawn(foreground.command, foreground.args, {
|
|
4792
4911
|
detached: true,
|
|
@@ -4817,7 +4936,7 @@ async function startDetachedGateway(input2) {
|
|
|
4817
4936
|
}
|
|
4818
4937
|
|
|
4819
4938
|
// src/multimodal/openai-compatible.ts
|
|
4820
|
-
import
|
|
4939
|
+
import fs14 from "fs/promises";
|
|
4821
4940
|
function normalizeBaseUrl2(baseUrl) {
|
|
4822
4941
|
return baseUrl.replace(/\/+$/, "");
|
|
4823
4942
|
}
|
|
@@ -4827,6 +4946,7 @@ function buildPrompt(context) {
|
|
|
4827
4946
|
"\u8BF7\u7406\u89E3\u8FD9\u5F20\u56FE\u7247\uFF0C\u5224\u65AD\u5B83\u662F\u5426\u5305\u542B\u503C\u5F97\u8FDB\u5165\u77E5\u8BC6\u5E93\u548C\u4F1A\u8BDD\u8BB0\u5FC6\u7684\u6709\u610F\u4E49\u4FE1\u606F\u3002",
|
|
4828
4947
|
'\u8BF7\u53EA\u8F93\u51FA JSON\uFF0C\u683C\u5F0F\u4E3A {"summary": string, "isMeaningful": boolean, "reason": string}\u3002',
|
|
4829
4948
|
"summary \u4F7F\u7528\u7B80\u6D01\u4E2D\u6587\u8F6C\u8FF0\u56FE\u7247\u4E2D\u7684\u5173\u952E\u4FE1\u606F\uFF1B\u65E0\u610F\u4E49\u56FE\u7247\u4E5F\u8981\u7ED9\u51FA\u7B80\u77ED summary\u3002",
|
|
4949
|
+
"\u5982\u679C\u4E0A\u4E0B\u6587\u63D0\u4F9B\u4E86\u56FE\u7247\u6587\u4EF6\u540D\uFF0Csummary \u5FC5\u987B\u539F\u6837\u5305\u542B\u8BE5\u6587\u4EF6\u540D\uFF0C\u4FBF\u4E8E\u4E4B\u540E\u6309\u6587\u4EF6\u540D\u68C0\u7D22\u548C\u53D1\u9001\u56FE\u7247\u3002",
|
|
4830
4950
|
contextText ? `\u4E0A\u4E0B\u6587\uFF1A${contextText}` : void 0
|
|
4831
4951
|
].filter(Boolean).join("\n");
|
|
4832
4952
|
}
|
|
@@ -4864,7 +4984,7 @@ var OpenAICompatibleMultimodalModel = class {
|
|
|
4864
4984
|
if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {
|
|
4865
4985
|
throw new Error("\u591A\u6A21\u6001\u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
|
|
4866
4986
|
}
|
|
4867
|
-
const image = await
|
|
4987
|
+
const image = await fs14.readFile(input2.imagePath);
|
|
4868
4988
|
const response = await fetch(`${normalizeBaseUrl2(this.options.baseUrl)}/chat/completions`, {
|
|
4869
4989
|
method: "POST",
|
|
4870
4990
|
headers: {
|
|
@@ -6398,7 +6518,7 @@ dev.command("ingest-feishu-event").description("\u4ECE JSON \u6587\u4EF6\u6A21\u
|
|
|
6398
6518
|
const config = await loadConfig();
|
|
6399
6519
|
const database = openDatabase(config);
|
|
6400
6520
|
try {
|
|
6401
|
-
const raw = await
|
|
6521
|
+
const raw = await fs15.readFile(options.file, "utf8");
|
|
6402
6522
|
const payload = JSON.parse(raw);
|
|
6403
6523
|
const result = new GatewayIngestor(database).ingestFeishuEvent(payload);
|
|
6404
6524
|
if (!result.accepted) {
|