chattercatcher 0.1.27 → 0.1.29
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 +464 -36
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +159 -87
- package/dist/index.js +451 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs15 from "fs/promises";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "chattercatcher",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.29",
|
|
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",
|
|
@@ -537,6 +537,18 @@ function migrateDatabase(database) {
|
|
|
537
537
|
|
|
538
538
|
CREATE INDEX IF NOT EXISTS image_multimodal_tasks_status_idx ON image_multimodal_tasks(status, updated_at);
|
|
539
539
|
|
|
540
|
+
CREATE TABLE IF NOT EXISTS feishu_chat_members (
|
|
541
|
+
chat_id TEXT NOT NULL,
|
|
542
|
+
open_id TEXT NOT NULL,
|
|
543
|
+
user_id TEXT,
|
|
544
|
+
user_name TEXT,
|
|
545
|
+
updated_at TEXT NOT NULL,
|
|
546
|
+
PRIMARY KEY (chat_id, open_id)
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
CREATE INDEX IF NOT EXISTS feishu_chat_members_chat_name_idx
|
|
550
|
+
ON feishu_chat_members(chat_id, user_name);
|
|
551
|
+
|
|
540
552
|
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
541
553
|
id TEXT PRIMARY KEY,
|
|
542
554
|
chat_id TEXT NOT NULL,
|
|
@@ -554,11 +566,29 @@ function migrateDatabase(database) {
|
|
|
554
566
|
|
|
555
567
|
CREATE INDEX IF NOT EXISTS cron_jobs_chat_status_idx ON cron_jobs(chat_id, status, updated_at);
|
|
556
568
|
CREATE INDEX IF NOT EXISTS cron_jobs_due_idx ON cron_jobs(status, next_run_at);
|
|
569
|
+
|
|
570
|
+
CREATE TABLE IF NOT EXISTS feishu_chat_members (
|
|
571
|
+
chat_id TEXT NOT NULL,
|
|
572
|
+
open_id TEXT NOT NULL,
|
|
573
|
+
user_id TEXT,
|
|
574
|
+
user_name TEXT NOT NULL,
|
|
575
|
+
updated_at TEXT NOT NULL,
|
|
576
|
+
PRIMARY KEY (chat_id, open_id)
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
CREATE INDEX IF NOT EXISTS feishu_chat_members_chat_name_idx
|
|
580
|
+
ON feishu_chat_members(chat_id, user_name);
|
|
557
581
|
`);
|
|
558
582
|
const cronJobColumns = database.prepare("PRAGMA table_info(cron_jobs)").all();
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
583
|
+
const ensureCronJobColumn = (name, definition) => {
|
|
584
|
+
if (!cronJobColumns.some((column) => column.name === name)) {
|
|
585
|
+
database.prepare(`ALTER TABLE cron_jobs ADD COLUMN ${definition}`).run();
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
ensureCronJobColumn("image_file_name", "image_file_name TEXT");
|
|
589
|
+
ensureCronJobColumn("mention_target_name", "mention_target_name TEXT");
|
|
590
|
+
ensureCronJobColumn("mention_open_id", "mention_open_id TEXT");
|
|
591
|
+
ensureCronJobColumn("mention_user_id", "mention_user_id TEXT");
|
|
562
592
|
}
|
|
563
593
|
|
|
564
594
|
// src/doctor/checks.ts
|
|
@@ -971,6 +1001,7 @@ function getGatewayStatus(config, secrets) {
|
|
|
971
1001
|
}
|
|
972
1002
|
|
|
973
1003
|
// src/llm/openai-compatible.ts
|
|
1004
|
+
var OPENAI_EMBEDDING_BATCH_SIZE = 64;
|
|
974
1005
|
function normalizeBaseUrl(baseUrl) {
|
|
975
1006
|
return baseUrl.replace(/\/+$/, "");
|
|
976
1007
|
}
|
|
@@ -1002,12 +1033,66 @@ function toOpenAITool(tool) {
|
|
|
1002
1033
|
}
|
|
1003
1034
|
};
|
|
1004
1035
|
}
|
|
1036
|
+
function parseToolCallArguments(value) {
|
|
1037
|
+
try {
|
|
1038
|
+
return JSON.parse(value);
|
|
1039
|
+
} catch {
|
|
1040
|
+
return {};
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function decodeDsmlValue(value, isString) {
|
|
1044
|
+
const trimmed = value.trim();
|
|
1045
|
+
if (isString) {
|
|
1046
|
+
return trimmed;
|
|
1047
|
+
}
|
|
1048
|
+
if (trimmed === "true") return true;
|
|
1049
|
+
if (trimmed === "false") return false;
|
|
1050
|
+
if (trimmed === "null") return null;
|
|
1051
|
+
const numberValue = Number(trimmed);
|
|
1052
|
+
if (trimmed && Number.isFinite(numberValue)) {
|
|
1053
|
+
return numberValue;
|
|
1054
|
+
}
|
|
1055
|
+
return trimmed;
|
|
1056
|
+
}
|
|
1057
|
+
function parseDsmlToolCalls(content) {
|
|
1058
|
+
if (!content?.includes("DSML")) {
|
|
1059
|
+
return [];
|
|
1060
|
+
}
|
|
1061
|
+
const toolCalls = [];
|
|
1062
|
+
const invokePattern = /<||DSML||invoke\s+name="([^"]+)"\s*>([\s\S]*?)<\/||DSML||invoke>/g;
|
|
1063
|
+
const parameterPattern = /<||DSML||parameter\s+name="([^"]+)"\s+string="(true|false)"\s*>([\s\S]*?)<\/||DSML||parameter>/g;
|
|
1064
|
+
for (const invoke of content.matchAll(invokePattern)) {
|
|
1065
|
+
const name = invoke[1];
|
|
1066
|
+
if (!name) {
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const input2 = {};
|
|
1070
|
+
const body = invoke[2] ?? "";
|
|
1071
|
+
for (const parameter of body.matchAll(parameterPattern)) {
|
|
1072
|
+
const parameterName = parameter[1];
|
|
1073
|
+
if (!parameterName) {
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
input2[parameterName] = decodeDsmlValue(parameter[3] ?? "", parameter[2] === "true");
|
|
1077
|
+
}
|
|
1078
|
+
toolCalls.push({
|
|
1079
|
+
id: `dsml_${toolCalls.length + 1}`,
|
|
1080
|
+
name,
|
|
1081
|
+
input: input2
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
return toolCalls;
|
|
1085
|
+
}
|
|
1005
1086
|
function parseToolCalls(message) {
|
|
1006
|
-
|
|
1087
|
+
const standardToolCalls = message?.tool_calls?.map((toolCall) => ({
|
|
1007
1088
|
id: toolCall.id,
|
|
1008
1089
|
name: toolCall.function.name,
|
|
1009
|
-
input:
|
|
1090
|
+
input: parseToolCallArguments(toolCall.function.arguments)
|
|
1010
1091
|
})) ?? [];
|
|
1092
|
+
return standardToolCalls.length > 0 ? standardToolCalls : parseDsmlToolCalls(message?.content);
|
|
1093
|
+
}
|
|
1094
|
+
function isDsmlToolCallContent(content) {
|
|
1095
|
+
return parseDsmlToolCalls(content).length > 0;
|
|
1011
1096
|
}
|
|
1012
1097
|
var OpenAICompatibleChatModel = class {
|
|
1013
1098
|
constructor(options) {
|
|
@@ -1066,9 +1151,10 @@ var OpenAICompatibleChatModel = class {
|
|
|
1066
1151
|
}
|
|
1067
1152
|
const data2 = await response.json();
|
|
1068
1153
|
const message = data2.choices?.[0]?.message;
|
|
1154
|
+
const toolCalls = parseToolCalls(message);
|
|
1069
1155
|
return {
|
|
1070
|
-
content: message?.content ?? "",
|
|
1071
|
-
toolCalls
|
|
1156
|
+
content: toolCalls.length > 0 && isDsmlToolCallContent(message?.content) ? "" : message?.content ?? "",
|
|
1157
|
+
toolCalls,
|
|
1072
1158
|
reasoningContent: message?.reasoning_content ?? void 0
|
|
1073
1159
|
};
|
|
1074
1160
|
}
|
|
@@ -1086,6 +1172,13 @@ var OpenAICompatibleEmbeddingModel = class {
|
|
|
1086
1172
|
if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {
|
|
1087
1173
|
throw new Error("Embedding \u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
|
|
1088
1174
|
}
|
|
1175
|
+
const vectors = [];
|
|
1176
|
+
for (let index2 = 0; index2 < texts.length; index2 += OPENAI_EMBEDDING_BATCH_SIZE) {
|
|
1177
|
+
vectors.push(...await this.fetchEmbeddingBatch(texts.slice(index2, index2 + OPENAI_EMBEDDING_BATCH_SIZE)));
|
|
1178
|
+
}
|
|
1179
|
+
return vectors;
|
|
1180
|
+
}
|
|
1181
|
+
async fetchEmbeddingBatch(texts) {
|
|
1089
1182
|
const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/embeddings`, {
|
|
1090
1183
|
method: "POST",
|
|
1091
1184
|
headers: {
|
|
@@ -2838,6 +2931,9 @@ var CronJobRepository = class {
|
|
|
2838
2931
|
const schedule = input2.schedule.trim();
|
|
2839
2932
|
const prompt = input2.prompt.trim();
|
|
2840
2933
|
const imageFileName = input2.imageFileName?.trim();
|
|
2934
|
+
const mentionTargetName = input2.mentionTargetName?.trim();
|
|
2935
|
+
const mentionOpenId = input2.mentionOpenId?.trim();
|
|
2936
|
+
const mentionUserId = input2.mentionUserId?.trim();
|
|
2841
2937
|
if (!isValidCronSchedule(schedule)) {
|
|
2842
2938
|
throw new Error("cron \u8868\u8FBE\u5F0F\u65E0\u6548\u3002");
|
|
2843
2939
|
}
|
|
@@ -2856,6 +2952,9 @@ var CronJobRepository = class {
|
|
|
2856
2952
|
schedule,
|
|
2857
2953
|
prompt,
|
|
2858
2954
|
...imageFileName ? { imageFileName } : {},
|
|
2955
|
+
...mentionTargetName ? { mentionTargetName } : {},
|
|
2956
|
+
...mentionOpenId ? { mentionOpenId } : {},
|
|
2957
|
+
...mentionUserId ? { mentionUserId } : {},
|
|
2859
2958
|
status: "active",
|
|
2860
2959
|
nextRunAt: nextRunAt.toISOString(),
|
|
2861
2960
|
createdAt: now.toISOString(),
|
|
@@ -2864,17 +2963,22 @@ var CronJobRepository = class {
|
|
|
2864
2963
|
this.database.prepare(
|
|
2865
2964
|
`
|
|
2866
2965
|
INSERT INTO cron_jobs (
|
|
2867
|
-
id, chat_id, created_by_open_id, schedule, prompt, image_file_name,
|
|
2966
|
+
id, chat_id, created_by_open_id, schedule, prompt, image_file_name,
|
|
2967
|
+
mention_target_name, mention_open_id, mention_user_id, status,
|
|
2868
2968
|
last_run_at, next_run_at, last_error, created_at, updated_at
|
|
2869
2969
|
)
|
|
2870
2970
|
VALUES (
|
|
2871
|
-
@id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName,
|
|
2971
|
+
@id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName,
|
|
2972
|
+
@mentionTargetName, @mentionOpenId, @mentionUserId, @status,
|
|
2872
2973
|
NULL, @nextRunAt, NULL, @createdAt, @updatedAt
|
|
2873
2974
|
)
|
|
2874
2975
|
`
|
|
2875
2976
|
).run({
|
|
2876
2977
|
...record,
|
|
2877
|
-
imageFileName: record.imageFileName ?? null
|
|
2978
|
+
imageFileName: record.imageFileName ?? null,
|
|
2979
|
+
mentionTargetName: record.mentionTargetName ?? null,
|
|
2980
|
+
mentionOpenId: record.mentionOpenId ?? null,
|
|
2981
|
+
mentionUserId: record.mentionUserId ?? null
|
|
2878
2982
|
});
|
|
2879
2983
|
return record;
|
|
2880
2984
|
}
|
|
@@ -2901,6 +3005,9 @@ var CronJobRepository = class {
|
|
|
2901
3005
|
schedule,
|
|
2902
3006
|
prompt,
|
|
2903
3007
|
image_file_name AS imageFileName,
|
|
3008
|
+
mention_target_name AS mentionTargetName,
|
|
3009
|
+
mention_open_id AS mentionOpenId,
|
|
3010
|
+
mention_user_id AS mentionUserId,
|
|
2904
3011
|
status,
|
|
2905
3012
|
last_run_at AS lastRunAt,
|
|
2906
3013
|
next_run_at AS nextRunAt,
|
|
@@ -2920,6 +3027,9 @@ var CronJobRepository = class {
|
|
|
2920
3027
|
schedule: row.schedule,
|
|
2921
3028
|
prompt: row.prompt,
|
|
2922
3029
|
imageFileName: row.imageFileName ?? void 0,
|
|
3030
|
+
mentionTargetName: row.mentionTargetName ?? void 0,
|
|
3031
|
+
mentionOpenId: row.mentionOpenId ?? void 0,
|
|
3032
|
+
mentionUserId: row.mentionUserId ?? void 0,
|
|
2923
3033
|
status: row.status,
|
|
2924
3034
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
2925
3035
|
nextRunAt: row.nextRunAt,
|
|
@@ -2994,6 +3104,9 @@ var CronJobRepository = class {
|
|
|
2994
3104
|
schedule,
|
|
2995
3105
|
prompt,
|
|
2996
3106
|
image_file_name AS imageFileName,
|
|
3107
|
+
mention_target_name AS mentionTargetName,
|
|
3108
|
+
mention_open_id AS mentionOpenId,
|
|
3109
|
+
mention_user_id AS mentionUserId,
|
|
2997
3110
|
status,
|
|
2998
3111
|
last_run_at AS lastRunAt,
|
|
2999
3112
|
next_run_at AS nextRunAt,
|
|
@@ -3013,6 +3126,9 @@ var CronJobRepository = class {
|
|
|
3013
3126
|
schedule: row.schedule,
|
|
3014
3127
|
prompt: row.prompt,
|
|
3015
3128
|
imageFileName: row.imageFileName ?? void 0,
|
|
3129
|
+
mentionTargetName: row.mentionTargetName ?? void 0,
|
|
3130
|
+
mentionOpenId: row.mentionOpenId ?? void 0,
|
|
3131
|
+
mentionUserId: row.mentionUserId ?? void 0,
|
|
3016
3132
|
status: row.status,
|
|
3017
3133
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
3018
3134
|
nextRunAt: row.nextRunAt,
|
|
@@ -3038,8 +3154,12 @@ async function generateCronJobMessage(input2) {
|
|
|
3038
3154
|
if (!input2.model.completeWithTools) {
|
|
3039
3155
|
throw new Error("\u5F53\u524D LLM \u5BA2\u6237\u7AEF\u4E0D\u652F\u6301\u5DE5\u5177\u8C03\u7528\u3002");
|
|
3040
3156
|
}
|
|
3157
|
+
const systemPrompt = input2.memberPrompt ? `${SYSTEM_PROMPT}
|
|
3158
|
+
|
|
3159
|
+
${input2.memberPrompt}
|
|
3160
|
+
\u751F\u6210\u6D88\u606F\u65F6\u9047\u5230\u4E0A\u8FF0 ID \u65F6\u4F18\u5148\u4F7F\u7528\u5BF9\u5E94\u7FA4\u6635\u79F0\uFF1B\u6CA1\u6709\u6620\u5C04\u65F6\u4FDD\u7559\u539F ID\uFF0C\u4E0D\u8981\u7F16\u9020\u6635\u79F0\u3002` : SYSTEM_PROMPT;
|
|
3041
3161
|
const messages = [
|
|
3042
|
-
{ role: "system", content:
|
|
3162
|
+
{ role: "system", content: systemPrompt },
|
|
3043
3163
|
{ role: "user", content: `\u5F53\u524D\u65F6\u95F4\uFF1A${input2.now.toISOString()}
|
|
3044
3164
|
\u4EFB\u52A1\u63D0\u793A\u8BCD\uFF1A${input2.prompt}` }
|
|
3045
3165
|
];
|
|
@@ -3057,7 +3177,7 @@ async function generateCronJobMessage(input2) {
|
|
|
3057
3177
|
for (const call of result.toolCalls) {
|
|
3058
3178
|
if (toolCallsUsed >= maxToolCalls) {
|
|
3059
3179
|
return input2.model.complete([
|
|
3060
|
-
{ role: "system", content:
|
|
3180
|
+
{ role: "system", content: systemPrompt },
|
|
3061
3181
|
{
|
|
3062
3182
|
role: "user",
|
|
3063
3183
|
content: `\u5F53\u524D\u65F6\u95F4\uFF1A${input2.now.toISOString()}
|
|
@@ -3116,7 +3236,12 @@ function createCronJobScheduler(options) {
|
|
|
3116
3236
|
for (const job of jobs) {
|
|
3117
3237
|
try {
|
|
3118
3238
|
const text = await options.generateMessage(job, startedAt);
|
|
3119
|
-
|
|
3239
|
+
const sendOptions = job.mentionOpenId && job.mentionTargetName ? { mentions: [{ openId: job.mentionOpenId, name: job.mentionTargetName }] } : void 0;
|
|
3240
|
+
if (sendOptions) {
|
|
3241
|
+
await options.sendTextToChat(job.chatId, text, sendOptions);
|
|
3242
|
+
} else {
|
|
3243
|
+
await options.sendTextToChat(job.chatId, text);
|
|
3244
|
+
}
|
|
3120
3245
|
if (job.imageFileName) {
|
|
3121
3246
|
if (!options.sendImageToChat) {
|
|
3122
3247
|
throw new Error("\u5F53\u524D\u5B9A\u65F6\u4EFB\u52A1\u8FD0\u884C\u73AF\u5883\u4E0D\u652F\u6301\u53D1\u9001\u56FE\u7247\u3002");
|
|
@@ -3263,12 +3388,20 @@ function parseExactNumber2(field, min, max) {
|
|
|
3263
3388
|
}
|
|
3264
3389
|
|
|
3265
3390
|
// src/rag/indexer.ts
|
|
3391
|
+
var EMBEDDING_INDEX_BATCH_SIZE = 64;
|
|
3266
3392
|
async function indexMessageChunks(input2) {
|
|
3267
3393
|
const chunks = input2.messageIds ? input2.messages.listMessageChunksByMessageIds(input2.messageIds, input2.limit ?? 1e4) : input2.messages.listAllMessageChunks(input2.limit ?? 1e4);
|
|
3268
3394
|
if (chunks.length === 0) {
|
|
3269
3395
|
return { chunks: 0, vectors: 0 };
|
|
3270
3396
|
}
|
|
3271
|
-
const vectors =
|
|
3397
|
+
const vectors = [];
|
|
3398
|
+
for (let index2 = 0; index2 < chunks.length; index2 += EMBEDDING_INDEX_BATCH_SIZE) {
|
|
3399
|
+
vectors.push(
|
|
3400
|
+
...await input2.embedding.embedBatch(
|
|
3401
|
+
chunks.slice(index2, index2 + EMBEDDING_INDEX_BATCH_SIZE).map((chunk) => chunk.text)
|
|
3402
|
+
)
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3272
3405
|
const records = [];
|
|
3273
3406
|
for (const [index2, chunk] of chunks.entries()) {
|
|
3274
3407
|
const vector = vectors[index2];
|
|
@@ -3606,6 +3739,185 @@ var ImageMultimodalWorker = class {
|
|
|
3606
3739
|
}
|
|
3607
3740
|
};
|
|
3608
3741
|
|
|
3742
|
+
// src/feishu/members.ts
|
|
3743
|
+
var DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
3744
|
+
var FeishuMemberRepository = class {
|
|
3745
|
+
constructor(database) {
|
|
3746
|
+
this.database = database;
|
|
3747
|
+
}
|
|
3748
|
+
database;
|
|
3749
|
+
upsert(record) {
|
|
3750
|
+
this.database.prepare(
|
|
3751
|
+
`
|
|
3752
|
+
INSERT INTO feishu_chat_members (chat_id, open_id, user_id, user_name, updated_at)
|
|
3753
|
+
VALUES (@chatId, @openId, @userId, @userName, @updatedAt)
|
|
3754
|
+
ON CONFLICT(chat_id, open_id)
|
|
3755
|
+
DO UPDATE SET
|
|
3756
|
+
user_id = excluded.user_id,
|
|
3757
|
+
user_name = excluded.user_name,
|
|
3758
|
+
updated_at = excluded.updated_at
|
|
3759
|
+
`
|
|
3760
|
+
).run({
|
|
3761
|
+
chatId: record.chatId,
|
|
3762
|
+
openId: record.openId,
|
|
3763
|
+
userId: record.userId ?? null,
|
|
3764
|
+
userName: record.userName,
|
|
3765
|
+
updatedAt: record.updatedAt
|
|
3766
|
+
});
|
|
3767
|
+
}
|
|
3768
|
+
get(chatId, openId) {
|
|
3769
|
+
const row = this.database.prepare(
|
|
3770
|
+
`
|
|
3771
|
+
SELECT
|
|
3772
|
+
chat_id AS chatId,
|
|
3773
|
+
open_id AS openId,
|
|
3774
|
+
user_id AS userId,
|
|
3775
|
+
user_name AS userName,
|
|
3776
|
+
updated_at AS updatedAt
|
|
3777
|
+
FROM feishu_chat_members
|
|
3778
|
+
WHERE chat_id = ? AND open_id = ?
|
|
3779
|
+
`
|
|
3780
|
+
).get(chatId, openId);
|
|
3781
|
+
return row ?? null;
|
|
3782
|
+
}
|
|
3783
|
+
listByChat(chatId) {
|
|
3784
|
+
return this.database.prepare(
|
|
3785
|
+
`
|
|
3786
|
+
SELECT
|
|
3787
|
+
chat_id AS chatId,
|
|
3788
|
+
open_id AS openId,
|
|
3789
|
+
user_id AS userId,
|
|
3790
|
+
user_name AS userName,
|
|
3791
|
+
updated_at AS updatedAt
|
|
3792
|
+
FROM feishu_chat_members
|
|
3793
|
+
WHERE chat_id = ?
|
|
3794
|
+
ORDER BY user_name ASC, open_id ASC
|
|
3795
|
+
`
|
|
3796
|
+
).all(chatId);
|
|
3797
|
+
}
|
|
3798
|
+
findUniqueByName(chatId, userName) {
|
|
3799
|
+
const rows = this.database.prepare(
|
|
3800
|
+
`
|
|
3801
|
+
SELECT
|
|
3802
|
+
chat_id AS chatId,
|
|
3803
|
+
open_id AS openId,
|
|
3804
|
+
user_id AS userId,
|
|
3805
|
+
user_name AS userName,
|
|
3806
|
+
updated_at AS updatedAt
|
|
3807
|
+
FROM feishu_chat_members
|
|
3808
|
+
WHERE chat_id = ? AND user_name = ?
|
|
3809
|
+
ORDER BY open_id ASC
|
|
3810
|
+
LIMIT 2
|
|
3811
|
+
`
|
|
3812
|
+
).all(chatId, userName);
|
|
3813
|
+
return rows.length === 1 ? rows[0] : null;
|
|
3814
|
+
}
|
|
3815
|
+
};
|
|
3816
|
+
var FeishuMemberResolver = class {
|
|
3817
|
+
constructor(options) {
|
|
3818
|
+
this.options = options;
|
|
3819
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3820
|
+
this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
3821
|
+
this.logger = options.logger;
|
|
3822
|
+
}
|
|
3823
|
+
options;
|
|
3824
|
+
now;
|
|
3825
|
+
ttlMs;
|
|
3826
|
+
logger;
|
|
3827
|
+
async resolveOpenIdName(chatId, openId) {
|
|
3828
|
+
const cached = this.options.repository.get(chatId, openId);
|
|
3829
|
+
if (!cached || this.isExpired(cached.updatedAt)) {
|
|
3830
|
+
try {
|
|
3831
|
+
await this.refreshChatMembers(chatId);
|
|
3832
|
+
} catch (error) {
|
|
3833
|
+
this.logger?.warn("Failed to refresh Feishu chat members for open id resolution", {
|
|
3834
|
+
chatId,
|
|
3835
|
+
openId,
|
|
3836
|
+
error
|
|
3837
|
+
});
|
|
3838
|
+
return cached?.userName ?? openId;
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
return this.options.repository.get(chatId, openId)?.userName ?? openId;
|
|
3842
|
+
}
|
|
3843
|
+
async resolveUniqueName(chatId, userName) {
|
|
3844
|
+
const cached = this.options.repository.findUniqueByName(chatId, userName);
|
|
3845
|
+
if (cached && !this.isExpired(cached.updatedAt)) {
|
|
3846
|
+
return cached;
|
|
3847
|
+
}
|
|
3848
|
+
try {
|
|
3849
|
+
await this.refreshChatMembers(chatId);
|
|
3850
|
+
} catch (error) {
|
|
3851
|
+
this.logger?.warn("Failed to refresh Feishu chat members for unique name resolution", {
|
|
3852
|
+
chatId,
|
|
3853
|
+
userName,
|
|
3854
|
+
error
|
|
3855
|
+
});
|
|
3856
|
+
return cached ?? null;
|
|
3857
|
+
}
|
|
3858
|
+
return this.options.repository.findUniqueByName(chatId, userName);
|
|
3859
|
+
}
|
|
3860
|
+
isExpired(updatedAt) {
|
|
3861
|
+
const updatedAtMs = Date.parse(updatedAt);
|
|
3862
|
+
if (Number.isNaN(updatedAtMs)) {
|
|
3863
|
+
return true;
|
|
3864
|
+
}
|
|
3865
|
+
return this.now().getTime() - updatedAtMs >= this.ttlMs;
|
|
3866
|
+
}
|
|
3867
|
+
async refreshChatMembers(chatId) {
|
|
3868
|
+
const members = await this.options.client.listChatMembers({ chatId, memberIdType: "open_id" });
|
|
3869
|
+
const updatedAt = this.now().toISOString();
|
|
3870
|
+
for (const member of members) {
|
|
3871
|
+
this.options.repository.upsert({
|
|
3872
|
+
chatId,
|
|
3873
|
+
openId: member.openId,
|
|
3874
|
+
userId: member.userId,
|
|
3875
|
+
userName: member.userName,
|
|
3876
|
+
updatedAt
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
};
|
|
3881
|
+
function formatFeishuMemberPrompt(members, limit = 80) {
|
|
3882
|
+
const lines = members.filter((member) => member.userName).slice(0, limit).map((member) => `${member.openId} = ${member.userName}`);
|
|
3883
|
+
return lines.length ? `\u5F53\u524D\u7FA4\u804A\u6210\u5458 ID \u4E0E\u7FA4\u6635\u79F0\u6620\u5C04\uFF1A
|
|
3884
|
+
${lines.join("\n")}` : "";
|
|
3885
|
+
}
|
|
3886
|
+
function createFeishuChatMembersClient(client) {
|
|
3887
|
+
return {
|
|
3888
|
+
async listChatMembers(payload) {
|
|
3889
|
+
const api = client.im.v1?.chatMembers?.get;
|
|
3890
|
+
if (!api) {
|
|
3891
|
+
throw new Error("\u5F53\u524D\u98DE\u4E66 SDK \u4E0D\u652F\u6301 chatMembers.get\uFF0C\u65E0\u6CD5\u83B7\u53D6\u7FA4\u6210\u5458\u3002");
|
|
3892
|
+
}
|
|
3893
|
+
const members = [];
|
|
3894
|
+
let pageToken;
|
|
3895
|
+
do {
|
|
3896
|
+
const response = await api({
|
|
3897
|
+
path: { chat_id: payload.chatId },
|
|
3898
|
+
params: {
|
|
3899
|
+
member_id_type: payload.memberIdType,
|
|
3900
|
+
...pageToken ? { page_token: pageToken } : {}
|
|
3901
|
+
}
|
|
3902
|
+
});
|
|
3903
|
+
const items = response.data?.items ?? [];
|
|
3904
|
+
for (const item of items) {
|
|
3905
|
+
if (!item.member_id || !item.name) {
|
|
3906
|
+
continue;
|
|
3907
|
+
}
|
|
3908
|
+
members.push({
|
|
3909
|
+
openId: item.member_id,
|
|
3910
|
+
userId: item.user_id,
|
|
3911
|
+
userName: item.name
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3914
|
+
pageToken = response.data?.has_more ? response.data.page_token : void 0;
|
|
3915
|
+
} while (pageToken);
|
|
3916
|
+
return members;
|
|
3917
|
+
}
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3609
3921
|
// src/cron/tools.ts
|
|
3610
3922
|
function readString(input2, key) {
|
|
3611
3923
|
const value = typeof input2 === "object" && input2 !== null && key in input2 ? input2[key] : void 0;
|
|
@@ -3644,18 +3956,27 @@ function createCronJobTools(input2) {
|
|
|
3644
3956
|
imageFileName: {
|
|
3645
3957
|
type: "string",
|
|
3646
3958
|
description: "Optional image filename already stored from the current chat, for example om_xxx-image.jpg."
|
|
3959
|
+
},
|
|
3960
|
+
mentionTargetName: {
|
|
3961
|
+
type: "string",
|
|
3962
|
+
description: "Optional exact Feishu chat nickname to @ when the scheduled message is sent."
|
|
3647
3963
|
}
|
|
3648
3964
|
},
|
|
3649
3965
|
required: ["schedule", "prompt"],
|
|
3650
3966
|
additionalProperties: false
|
|
3651
3967
|
},
|
|
3652
3968
|
execute: async (rawInput) => {
|
|
3969
|
+
const mentionTargetName = readOptionalString(rawInput, "mentionTargetName");
|
|
3970
|
+
const mentionTarget = mentionTargetName && input2.memberResolver ? await input2.memberResolver.resolveUniqueName(input2.chatId, mentionTargetName) : null;
|
|
3653
3971
|
const job = input2.repository.create({
|
|
3654
3972
|
chatId: input2.chatId,
|
|
3655
3973
|
createdByOpenId: input2.createdByOpenId,
|
|
3656
3974
|
schedule: readString(rawInput, "schedule"),
|
|
3657
3975
|
prompt: readString(rawInput, "prompt"),
|
|
3658
|
-
imageFileName: readOptionalString(rawInput, "imageFileName")
|
|
3976
|
+
imageFileName: readOptionalString(rawInput, "imageFileName"),
|
|
3977
|
+
mentionTargetName,
|
|
3978
|
+
mentionOpenId: mentionTarget?.openId,
|
|
3979
|
+
mentionUserId: mentionTarget?.userId
|
|
3659
3980
|
});
|
|
3660
3981
|
return JSON.stringify({ ok: true, job });
|
|
3661
3982
|
}
|
|
@@ -3823,6 +4144,9 @@ var DEFAULT_MAX_MODEL_TURNS = 4;
|
|
|
3823
4144
|
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
3824
4145
|
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";
|
|
3825
4146
|
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";
|
|
4147
|
+
function containsRawToolCallMarkup(content) {
|
|
4148
|
+
return /<||DSML||tool_calls>|<||DSML||invoke\s+name=|<tool_call>|<tool_calls>/i.test(content);
|
|
4149
|
+
}
|
|
3826
4150
|
function toToolResultContent(value) {
|
|
3827
4151
|
if (typeof value === "string") return value;
|
|
3828
4152
|
return JSON.stringify(value);
|
|
@@ -3856,8 +4180,12 @@ async function runFeishuToolLoop(input2) {
|
|
|
3856
4180
|
}
|
|
3857
4181
|
const maxModelTurns = input2.maxModelTurns ?? DEFAULT_MAX_MODEL_TURNS;
|
|
3858
4182
|
const maxToolCalls = input2.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
4183
|
+
const systemPrompt = input2.memberPrompt ? `${FEISHU_TOOL_SYSTEM_PROMPT}
|
|
4184
|
+
|
|
4185
|
+
${input2.memberPrompt}
|
|
4186
|
+
\u56DE\u7B54\u4E2D\u9047\u5230\u4E0A\u8FF0 ID \u65F6\u4F18\u5148\u4F7F\u7528\u5BF9\u5E94\u7FA4\u6635\u79F0\uFF1B\u6CA1\u6709\u6620\u5C04\u65F6\u4FDD\u7559\u539F ID\uFF0C\u4E0D\u8981\u7F16\u9020\u6635\u79F0\u3002` : FEISHU_TOOL_SYSTEM_PROMPT;
|
|
3859
4187
|
const messages = [
|
|
3860
|
-
{ role: "system", content:
|
|
4188
|
+
{ role: "system", content: systemPrompt },
|
|
3861
4189
|
{ role: "user", content: `\u5F53\u524D\u65F6\u95F4\uFF1A${input2.now.toISOString()}
|
|
3862
4190
|
\u95EE\u9898\uFF1A${input2.question}` }
|
|
3863
4191
|
];
|
|
@@ -3865,6 +4193,7 @@ async function runFeishuToolLoop(input2) {
|
|
|
3865
4193
|
let toolCallsUsed = 0;
|
|
3866
4194
|
for (let turn = 0; turn < maxModelTurns; turn += 1) {
|
|
3867
4195
|
const assistantResult = await input2.model.completeWithTools(messages, input2.tools);
|
|
4196
|
+
const hasRawToolCallMarkup = containsRawToolCallMarkup(assistantResult.content);
|
|
3868
4197
|
messages.push({
|
|
3869
4198
|
role: "assistant",
|
|
3870
4199
|
content: assistantResult.content,
|
|
@@ -3872,6 +4201,9 @@ async function runFeishuToolLoop(input2) {
|
|
|
3872
4201
|
reasoningContent: assistantResult.reasoningContent
|
|
3873
4202
|
});
|
|
3874
4203
|
if (assistantResult.toolCalls.length === 0) {
|
|
4204
|
+
if (hasRawToolCallMarkup) {
|
|
4205
|
+
break;
|
|
4206
|
+
}
|
|
3875
4207
|
return assistantResult.content || FEISHU_TOOL_LOOP_FALLBACK;
|
|
3876
4208
|
}
|
|
3877
4209
|
for (const toolCall of assistantResult.toolCalls) {
|
|
@@ -3965,8 +4297,13 @@ function getFeishuQuestionDecision(payload, config) {
|
|
|
3965
4297
|
var FeishuQuestionHandler = class {
|
|
3966
4298
|
constructor(options) {
|
|
3967
4299
|
this.options = options;
|
|
4300
|
+
this.memberResolver = options.memberResolver;
|
|
3968
4301
|
}
|
|
3969
4302
|
options;
|
|
4303
|
+
memberResolver;
|
|
4304
|
+
setMemberResolver(memberResolver) {
|
|
4305
|
+
this.memberResolver = memberResolver;
|
|
4306
|
+
}
|
|
3970
4307
|
async sendResponse(chatId, messageId, text) {
|
|
3971
4308
|
if (messageId && this.options.sender.replyTextToMessage) {
|
|
3972
4309
|
try {
|
|
@@ -3984,7 +4321,7 @@ var FeishuQuestionHandler = class {
|
|
|
3984
4321
|
}
|
|
3985
4322
|
if (this.options.sender.addReactionToMessage) {
|
|
3986
4323
|
try {
|
|
3987
|
-
await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? "
|
|
4324
|
+
await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? "OK");
|
|
3988
4325
|
return;
|
|
3989
4326
|
} catch (error) {
|
|
3990
4327
|
console.log(`\u98DE\u4E66\u63D0\u95EE\u8868\u60C5\u53CD\u9988\u5931\u8D25\uFF0C\u6539\u7528\u6587\u5B57\u53CD\u9988\uFF1A${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -4013,14 +4350,18 @@ var FeishuQuestionHandler = class {
|
|
|
4013
4350
|
const cronTools = createCronJobTools({
|
|
4014
4351
|
repository: new CronJobRepository(this.options.database),
|
|
4015
4352
|
chatId: decision.chatId,
|
|
4016
|
-
createdByOpenId: payload.event?.sender?.sender_id?.open_id
|
|
4353
|
+
createdByOpenId: payload.event?.sender?.sender_id?.open_id,
|
|
4354
|
+
memberResolver: this.memberResolver
|
|
4017
4355
|
});
|
|
4018
4356
|
const allTools = [...tools, ...cronTools];
|
|
4357
|
+
const memberRepository = this.options.memberRepository ?? new FeishuMemberRepository(this.options.database);
|
|
4358
|
+
const memberPrompt = formatFeishuMemberPrompt(memberRepository.listByChat(decision.chatId));
|
|
4019
4359
|
const answer = await runFeishuToolLoop({
|
|
4020
4360
|
question: decision.question,
|
|
4021
4361
|
now,
|
|
4022
4362
|
tools: allTools,
|
|
4023
|
-
model: this.options.model
|
|
4363
|
+
model: this.options.model,
|
|
4364
|
+
memberPrompt
|
|
4024
4365
|
});
|
|
4025
4366
|
qaLogs.create({
|
|
4026
4367
|
chatId: decision.chatId,
|
|
@@ -4073,6 +4414,15 @@ function extractImageKey(response) {
|
|
|
4073
4414
|
}
|
|
4074
4415
|
throw new Error("\u98DE\u4E66\u56FE\u7247\u4E0A\u4F20\u54CD\u5E94\u7F3A\u5C11 image_key\u3002");
|
|
4075
4416
|
}
|
|
4417
|
+
function escapeAtText(value) {
|
|
4418
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4419
|
+
}
|
|
4420
|
+
function formatTextWithMentions(text, options) {
|
|
4421
|
+
const mentions = options?.mentions ?? [];
|
|
4422
|
+
if (mentions.length === 0) return text;
|
|
4423
|
+
const prefix = mentions.map((mention) => `<at user_id="${escapeAtText(mention.openId)}">${escapeAtText(mention.name)}</at>`).join(" ");
|
|
4424
|
+
return `${prefix} ${text}`.trim();
|
|
4425
|
+
}
|
|
4076
4426
|
var FeishuMessageSender = class _FeishuMessageSender {
|
|
4077
4427
|
constructor(client) {
|
|
4078
4428
|
this.client = client;
|
|
@@ -4086,12 +4436,12 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4086
4436
|
});
|
|
4087
4437
|
return new _FeishuMessageSender(client);
|
|
4088
4438
|
}
|
|
4089
|
-
async sendTextToChat(chatId, text) {
|
|
4439
|
+
async sendTextToChat(chatId, text, options) {
|
|
4090
4440
|
const payload = {
|
|
4091
4441
|
data: {
|
|
4092
4442
|
receive_id: chatId,
|
|
4093
4443
|
msg_type: "text",
|
|
4094
|
-
content: JSON.stringify({ text })
|
|
4444
|
+
content: JSON.stringify({ text: formatTextWithMentions(text, options) })
|
|
4095
4445
|
},
|
|
4096
4446
|
params: {
|
|
4097
4447
|
receive_id_type: "chat_id"
|
|
@@ -4213,13 +4563,24 @@ function createFeishuEventDispatcher(options) {
|
|
|
4213
4563
|
return;
|
|
4214
4564
|
}
|
|
4215
4565
|
}
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4566
|
+
let result;
|
|
4567
|
+
if (options.resourceDownloader) {
|
|
4568
|
+
result = await options.ingestor.ingestFeishuEventAndDownloadAttachments({
|
|
4569
|
+
payload,
|
|
4570
|
+
downloader: options.resourceDownloader,
|
|
4571
|
+
config: options.config,
|
|
4572
|
+
secrets: options.secrets,
|
|
4573
|
+
vectorIndexMessage: options.attachmentVectorIndexer,
|
|
4574
|
+
memberResolver: options.memberResolver
|
|
4575
|
+
});
|
|
4576
|
+
} else if (options.memberResolver) {
|
|
4577
|
+
result = await options.ingestor.ingestFeishuEventWithMembers({
|
|
4578
|
+
payload,
|
|
4579
|
+
memberResolver: options.memberResolver
|
|
4580
|
+
});
|
|
4581
|
+
} else {
|
|
4582
|
+
result = options.ingestor.ingestFeishuEvent(payload);
|
|
4583
|
+
}
|
|
4223
4584
|
if (!result.accepted) {
|
|
4224
4585
|
console.log(`\u98DE\u4E66\u6D88\u606F\u672A\u5165\u5E93\uFF1A${result.reason}`);
|
|
4225
4586
|
return;
|
|
@@ -4310,10 +4671,20 @@ function createFeishuGateway(options) {
|
|
|
4310
4671
|
onReconnecting: () => console.log("\u98DE\u4E66\u957F\u8FDE\u63A5\u6B63\u5728\u91CD\u8FDE\u3002"),
|
|
4311
4672
|
onReconnected: () => console.log("\u98DE\u4E66\u957F\u8FDE\u63A5\u5DF2\u91CD\u8FDE\u3002")
|
|
4312
4673
|
});
|
|
4674
|
+
const memberResolver = new FeishuMemberResolver({
|
|
4675
|
+
repository: new FeishuMemberRepository(options.ingestor.database),
|
|
4676
|
+
client: createFeishuChatMembersClient(new lark2.Client({
|
|
4677
|
+
appId: options.config.feishu.appId,
|
|
4678
|
+
appSecret: options.secrets.feishu.appSecret,
|
|
4679
|
+
domain: mapDomain(options.config.feishu.domain)
|
|
4680
|
+
}))
|
|
4681
|
+
});
|
|
4682
|
+
options.questionHandler?.setMemberResolver?.(memberResolver);
|
|
4313
4683
|
const eventDispatcher = createFeishuEventDispatcher({
|
|
4314
4684
|
config: options.config,
|
|
4315
4685
|
secrets: options.secrets,
|
|
4316
4686
|
ingestor: options.ingestor,
|
|
4687
|
+
memberResolver,
|
|
4317
4688
|
questionHandler: options.questionHandler,
|
|
4318
4689
|
resourceDownloader: options.resourceDownloader,
|
|
4319
4690
|
attachmentVectorIndexer: options.attachmentVectorIndexer,
|
|
@@ -4333,7 +4704,7 @@ function createFeishuGateway(options) {
|
|
|
4333
4704
|
}) : void 0);
|
|
4334
4705
|
const cronJobScheduler = options.cronJobScheduler ?? (options.cronJobProcessor ? createCronJobScheduler({
|
|
4335
4706
|
repository: new CronJobRepository(options.cronJobProcessor.database),
|
|
4336
|
-
sendTextToChat: (chatId, text) => options.cronJobProcessor.sender.sendTextToChat(chatId, text),
|
|
4707
|
+
sendTextToChat: (chatId, text, sendOptions) => options.cronJobProcessor.sender.sendTextToChat(chatId, text, sendOptions),
|
|
4337
4708
|
sendImageToChat: options.cronJobProcessor.sender.sendImageToChat ? (chatId, imageFileName) => options.cronJobProcessor.sender.sendImageToChat(
|
|
4338
4709
|
chatId,
|
|
4339
4710
|
resolveFeishuImagePath(options.config, imageFileName)
|
|
@@ -4347,7 +4718,16 @@ function createFeishuGateway(options) {
|
|
|
4347
4718
|
scope: { platform: "feishu", platformChatId: job.chatId }
|
|
4348
4719
|
});
|
|
4349
4720
|
try {
|
|
4350
|
-
|
|
4721
|
+
const memberPrompt = formatFeishuMemberPrompt(
|
|
4722
|
+
new FeishuMemberRepository(options.cronJobProcessor.database).listByChat(job.chatId)
|
|
4723
|
+
);
|
|
4724
|
+
return await generateCronJobMessage({
|
|
4725
|
+
prompt: job.prompt,
|
|
4726
|
+
model: options.cronJobProcessor.model,
|
|
4727
|
+
tools,
|
|
4728
|
+
now,
|
|
4729
|
+
memberPrompt
|
|
4730
|
+
});
|
|
4351
4731
|
} finally {
|
|
4352
4732
|
close();
|
|
4353
4733
|
}
|
|
@@ -4705,7 +5085,9 @@ function normalizeFeishuReceiveMessageEvent(payload) {
|
|
|
4705
5085
|
if (!text) {
|
|
4706
5086
|
return null;
|
|
4707
5087
|
}
|
|
4708
|
-
const
|
|
5088
|
+
const senderOpenId = event.sender?.sender_id?.open_id;
|
|
5089
|
+
const senderUserId = event.sender?.sender_id?.user_id;
|
|
5090
|
+
const senderId = senderOpenId || senderUserId || event.sender?.sender_id?.union_id || "unknown";
|
|
4709
5091
|
return {
|
|
4710
5092
|
platform: "feishu",
|
|
4711
5093
|
platformChatId: message.chat_id,
|
|
@@ -4720,12 +5102,24 @@ function normalizeFeishuReceiveMessageEvent(payload) {
|
|
|
4720
5102
|
platform: "feishu",
|
|
4721
5103
|
raw: payload,
|
|
4722
5104
|
content,
|
|
5105
|
+
sender: {
|
|
5106
|
+
...senderOpenId ? { openId: senderOpenId } : {},
|
|
5107
|
+
...senderUserId ? { userId: senderUserId } : {}
|
|
5108
|
+
},
|
|
4723
5109
|
attachment: extractFeishuAttachment(messageType, content)
|
|
4724
5110
|
}
|
|
4725
5111
|
};
|
|
4726
5112
|
}
|
|
4727
5113
|
|
|
4728
5114
|
// src/gateway/ingest.ts
|
|
5115
|
+
function extractFeishuSenderOpenId(message) {
|
|
5116
|
+
const raw = message.rawPayload;
|
|
5117
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
5118
|
+
const sender = raw.sender;
|
|
5119
|
+
if (!sender || typeof sender !== "object" || Array.isArray(sender)) return void 0;
|
|
5120
|
+
const openId = sender.openId;
|
|
5121
|
+
return typeof openId === "string" && openId.trim() ? openId.trim() : void 0;
|
|
5122
|
+
}
|
|
4729
5123
|
function extractAttachment(message) {
|
|
4730
5124
|
const raw = message.rawPayload;
|
|
4731
5125
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -4745,14 +5139,16 @@ function isMultimodalReady(config, secrets) {
|
|
|
4745
5139
|
return Boolean(config.multimodal.baseUrl && config.multimodal.model && secrets.multimodal.apiKey);
|
|
4746
5140
|
}
|
|
4747
5141
|
var GatewayIngestor = class {
|
|
4748
|
-
messages;
|
|
4749
|
-
jobs;
|
|
4750
|
-
imageTasks;
|
|
4751
5142
|
constructor(database) {
|
|
5143
|
+
this.database = database;
|
|
4752
5144
|
this.messages = new MessageRepository(database);
|
|
4753
5145
|
this.jobs = new FileJobRepository(database);
|
|
4754
5146
|
this.imageTasks = new ImageMultimodalTaskRepository(database);
|
|
4755
5147
|
}
|
|
5148
|
+
database;
|
|
5149
|
+
messages;
|
|
5150
|
+
jobs;
|
|
5151
|
+
imageTasks;
|
|
4756
5152
|
ingestFeishuEvent(payload) {
|
|
4757
5153
|
const normalized = normalizeFeishuReceiveMessageEvent(payload);
|
|
4758
5154
|
if (!normalized) {
|
|
@@ -4770,8 +5166,28 @@ var GatewayIngestor = class {
|
|
|
4770
5166
|
duplicate
|
|
4771
5167
|
};
|
|
4772
5168
|
}
|
|
5169
|
+
async ingestFeishuEventWithMembers(input2) {
|
|
5170
|
+
const normalized = normalizeFeishuReceiveMessageEvent(input2.payload);
|
|
5171
|
+
if (!normalized) {
|
|
5172
|
+
return {
|
|
5173
|
+
accepted: false,
|
|
5174
|
+
reason: "\u4E8B\u4EF6\u4E0D\u662F\u53EF\u5165\u5E93\u7684\u98DE\u4E66\u6D88\u606F\u3002"
|
|
5175
|
+
};
|
|
5176
|
+
}
|
|
5177
|
+
const openId = extractFeishuSenderOpenId(normalized);
|
|
5178
|
+
const senderName = openId ? await input2.memberResolver.resolveOpenIdName(normalized.platformChatId, openId) : normalized.senderName;
|
|
5179
|
+
const enriched = { ...normalized, senderName };
|
|
5180
|
+
const duplicate = this.messages.hasPlatformMessage(enriched.platform, enriched.platformMessageId);
|
|
5181
|
+
const messageId = this.messages.ingest(enriched);
|
|
5182
|
+
return {
|
|
5183
|
+
accepted: true,
|
|
5184
|
+
messageId,
|
|
5185
|
+
message: enriched,
|
|
5186
|
+
duplicate
|
|
5187
|
+
};
|
|
5188
|
+
}
|
|
4773
5189
|
async ingestFeishuEventAndDownloadAttachments(input2) {
|
|
4774
|
-
const result = this.ingestFeishuEvent(input2.payload);
|
|
5190
|
+
const result = input2.memberResolver ? await this.ingestFeishuEventWithMembers({ payload: input2.payload, memberResolver: input2.memberResolver }) : this.ingestFeishuEvent(input2.payload);
|
|
4775
5191
|
if (!result.accepted || !result.messageId || !result.message || result.duplicate) {
|
|
4776
5192
|
return result;
|
|
4777
5193
|
}
|
|
@@ -5026,6 +5442,9 @@ function createMultimodalModel(config, secrets) {
|
|
|
5026
5442
|
});
|
|
5027
5443
|
}
|
|
5028
5444
|
|
|
5445
|
+
// src/cli.ts
|
|
5446
|
+
import * as lark4 from "@larksuiteoapi/node-sdk";
|
|
5447
|
+
|
|
5029
5448
|
// src/rag/answer.ts
|
|
5030
5449
|
var DEFAULT_MAX_EVIDENCE_BLOCKS = 8;
|
|
5031
5450
|
var DEFAULT_MAX_CHARS_PER_BLOCK = 1200;
|
|
@@ -6123,6 +6542,14 @@ async function startGatewayForegroundCommand() {
|
|
|
6123
6542
|
const database = openDatabase(config);
|
|
6124
6543
|
const chatModel = createChatModel(config, secrets);
|
|
6125
6544
|
const sender = FeishuMessageSender.fromConfig(config, secrets);
|
|
6545
|
+
const memberResolver = new FeishuMemberResolver({
|
|
6546
|
+
repository: new FeishuMemberRepository(database),
|
|
6547
|
+
client: createFeishuChatMembersClient(new lark4.Client({
|
|
6548
|
+
appId: config.feishu.appId,
|
|
6549
|
+
appSecret: secrets.feishu.appSecret,
|
|
6550
|
+
domain: mapDomain(config.feishu.domain)
|
|
6551
|
+
}))
|
|
6552
|
+
});
|
|
6126
6553
|
const vectorStore = hasEmbeddingConfig(config, secrets) ? new SqliteVectorStore(database, { model: config.embedding.model }) : null;
|
|
6127
6554
|
const gatewayRuntime = createFeishuGateway({
|
|
6128
6555
|
config,
|
|
@@ -6155,6 +6582,7 @@ async function startGatewayForegroundCommand() {
|
|
|
6155
6582
|
config,
|
|
6156
6583
|
secrets,
|
|
6157
6584
|
database,
|
|
6585
|
+
memberResolver,
|
|
6158
6586
|
sender,
|
|
6159
6587
|
model: chatModel
|
|
6160
6588
|
})
|