chattercatcher 0.1.28 → 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 +380 -30
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +158 -87
- package/dist/index.js +367 -29
- 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
|
|
@@ -2901,6 +2931,9 @@ var CronJobRepository = class {
|
|
|
2901
2931
|
const schedule = input2.schedule.trim();
|
|
2902
2932
|
const prompt = input2.prompt.trim();
|
|
2903
2933
|
const imageFileName = input2.imageFileName?.trim();
|
|
2934
|
+
const mentionTargetName = input2.mentionTargetName?.trim();
|
|
2935
|
+
const mentionOpenId = input2.mentionOpenId?.trim();
|
|
2936
|
+
const mentionUserId = input2.mentionUserId?.trim();
|
|
2904
2937
|
if (!isValidCronSchedule(schedule)) {
|
|
2905
2938
|
throw new Error("cron \u8868\u8FBE\u5F0F\u65E0\u6548\u3002");
|
|
2906
2939
|
}
|
|
@@ -2919,6 +2952,9 @@ var CronJobRepository = class {
|
|
|
2919
2952
|
schedule,
|
|
2920
2953
|
prompt,
|
|
2921
2954
|
...imageFileName ? { imageFileName } : {},
|
|
2955
|
+
...mentionTargetName ? { mentionTargetName } : {},
|
|
2956
|
+
...mentionOpenId ? { mentionOpenId } : {},
|
|
2957
|
+
...mentionUserId ? { mentionUserId } : {},
|
|
2922
2958
|
status: "active",
|
|
2923
2959
|
nextRunAt: nextRunAt.toISOString(),
|
|
2924
2960
|
createdAt: now.toISOString(),
|
|
@@ -2927,17 +2963,22 @@ var CronJobRepository = class {
|
|
|
2927
2963
|
this.database.prepare(
|
|
2928
2964
|
`
|
|
2929
2965
|
INSERT INTO cron_jobs (
|
|
2930
|
-
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,
|
|
2931
2968
|
last_run_at, next_run_at, last_error, created_at, updated_at
|
|
2932
2969
|
)
|
|
2933
2970
|
VALUES (
|
|
2934
|
-
@id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName,
|
|
2971
|
+
@id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName,
|
|
2972
|
+
@mentionTargetName, @mentionOpenId, @mentionUserId, @status,
|
|
2935
2973
|
NULL, @nextRunAt, NULL, @createdAt, @updatedAt
|
|
2936
2974
|
)
|
|
2937
2975
|
`
|
|
2938
2976
|
).run({
|
|
2939
2977
|
...record,
|
|
2940
|
-
imageFileName: record.imageFileName ?? null
|
|
2978
|
+
imageFileName: record.imageFileName ?? null,
|
|
2979
|
+
mentionTargetName: record.mentionTargetName ?? null,
|
|
2980
|
+
mentionOpenId: record.mentionOpenId ?? null,
|
|
2981
|
+
mentionUserId: record.mentionUserId ?? null
|
|
2941
2982
|
});
|
|
2942
2983
|
return record;
|
|
2943
2984
|
}
|
|
@@ -2964,6 +3005,9 @@ var CronJobRepository = class {
|
|
|
2964
3005
|
schedule,
|
|
2965
3006
|
prompt,
|
|
2966
3007
|
image_file_name AS imageFileName,
|
|
3008
|
+
mention_target_name AS mentionTargetName,
|
|
3009
|
+
mention_open_id AS mentionOpenId,
|
|
3010
|
+
mention_user_id AS mentionUserId,
|
|
2967
3011
|
status,
|
|
2968
3012
|
last_run_at AS lastRunAt,
|
|
2969
3013
|
next_run_at AS nextRunAt,
|
|
@@ -2983,6 +3027,9 @@ var CronJobRepository = class {
|
|
|
2983
3027
|
schedule: row.schedule,
|
|
2984
3028
|
prompt: row.prompt,
|
|
2985
3029
|
imageFileName: row.imageFileName ?? void 0,
|
|
3030
|
+
mentionTargetName: row.mentionTargetName ?? void 0,
|
|
3031
|
+
mentionOpenId: row.mentionOpenId ?? void 0,
|
|
3032
|
+
mentionUserId: row.mentionUserId ?? void 0,
|
|
2986
3033
|
status: row.status,
|
|
2987
3034
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
2988
3035
|
nextRunAt: row.nextRunAt,
|
|
@@ -3057,6 +3104,9 @@ var CronJobRepository = class {
|
|
|
3057
3104
|
schedule,
|
|
3058
3105
|
prompt,
|
|
3059
3106
|
image_file_name AS imageFileName,
|
|
3107
|
+
mention_target_name AS mentionTargetName,
|
|
3108
|
+
mention_open_id AS mentionOpenId,
|
|
3109
|
+
mention_user_id AS mentionUserId,
|
|
3060
3110
|
status,
|
|
3061
3111
|
last_run_at AS lastRunAt,
|
|
3062
3112
|
next_run_at AS nextRunAt,
|
|
@@ -3076,6 +3126,9 @@ var CronJobRepository = class {
|
|
|
3076
3126
|
schedule: row.schedule,
|
|
3077
3127
|
prompt: row.prompt,
|
|
3078
3128
|
imageFileName: row.imageFileName ?? void 0,
|
|
3129
|
+
mentionTargetName: row.mentionTargetName ?? void 0,
|
|
3130
|
+
mentionOpenId: row.mentionOpenId ?? void 0,
|
|
3131
|
+
mentionUserId: row.mentionUserId ?? void 0,
|
|
3079
3132
|
status: row.status,
|
|
3080
3133
|
lastRunAt: row.lastRunAt ?? void 0,
|
|
3081
3134
|
nextRunAt: row.nextRunAt,
|
|
@@ -3101,8 +3154,12 @@ async function generateCronJobMessage(input2) {
|
|
|
3101
3154
|
if (!input2.model.completeWithTools) {
|
|
3102
3155
|
throw new Error("\u5F53\u524D LLM \u5BA2\u6237\u7AEF\u4E0D\u652F\u6301\u5DE5\u5177\u8C03\u7528\u3002");
|
|
3103
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;
|
|
3104
3161
|
const messages = [
|
|
3105
|
-
{ role: "system", content:
|
|
3162
|
+
{ role: "system", content: systemPrompt },
|
|
3106
3163
|
{ role: "user", content: `\u5F53\u524D\u65F6\u95F4\uFF1A${input2.now.toISOString()}
|
|
3107
3164
|
\u4EFB\u52A1\u63D0\u793A\u8BCD\uFF1A${input2.prompt}` }
|
|
3108
3165
|
];
|
|
@@ -3120,7 +3177,7 @@ async function generateCronJobMessage(input2) {
|
|
|
3120
3177
|
for (const call of result.toolCalls) {
|
|
3121
3178
|
if (toolCallsUsed >= maxToolCalls) {
|
|
3122
3179
|
return input2.model.complete([
|
|
3123
|
-
{ role: "system", content:
|
|
3180
|
+
{ role: "system", content: systemPrompt },
|
|
3124
3181
|
{
|
|
3125
3182
|
role: "user",
|
|
3126
3183
|
content: `\u5F53\u524D\u65F6\u95F4\uFF1A${input2.now.toISOString()}
|
|
@@ -3179,7 +3236,12 @@ function createCronJobScheduler(options) {
|
|
|
3179
3236
|
for (const job of jobs) {
|
|
3180
3237
|
try {
|
|
3181
3238
|
const text = await options.generateMessage(job, startedAt);
|
|
3182
|
-
|
|
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
|
+
}
|
|
3183
3245
|
if (job.imageFileName) {
|
|
3184
3246
|
if (!options.sendImageToChat) {
|
|
3185
3247
|
throw new Error("\u5F53\u524D\u5B9A\u65F6\u4EFB\u52A1\u8FD0\u884C\u73AF\u5883\u4E0D\u652F\u6301\u53D1\u9001\u56FE\u7247\u3002");
|
|
@@ -3677,6 +3739,185 @@ var ImageMultimodalWorker = class {
|
|
|
3677
3739
|
}
|
|
3678
3740
|
};
|
|
3679
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
|
+
|
|
3680
3921
|
// src/cron/tools.ts
|
|
3681
3922
|
function readString(input2, key) {
|
|
3682
3923
|
const value = typeof input2 === "object" && input2 !== null && key in input2 ? input2[key] : void 0;
|
|
@@ -3715,18 +3956,27 @@ function createCronJobTools(input2) {
|
|
|
3715
3956
|
imageFileName: {
|
|
3716
3957
|
type: "string",
|
|
3717
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."
|
|
3718
3963
|
}
|
|
3719
3964
|
},
|
|
3720
3965
|
required: ["schedule", "prompt"],
|
|
3721
3966
|
additionalProperties: false
|
|
3722
3967
|
},
|
|
3723
3968
|
execute: async (rawInput) => {
|
|
3969
|
+
const mentionTargetName = readOptionalString(rawInput, "mentionTargetName");
|
|
3970
|
+
const mentionTarget = mentionTargetName && input2.memberResolver ? await input2.memberResolver.resolveUniqueName(input2.chatId, mentionTargetName) : null;
|
|
3724
3971
|
const job = input2.repository.create({
|
|
3725
3972
|
chatId: input2.chatId,
|
|
3726
3973
|
createdByOpenId: input2.createdByOpenId,
|
|
3727
3974
|
schedule: readString(rawInput, "schedule"),
|
|
3728
3975
|
prompt: readString(rawInput, "prompt"),
|
|
3729
|
-
imageFileName: readOptionalString(rawInput, "imageFileName")
|
|
3976
|
+
imageFileName: readOptionalString(rawInput, "imageFileName"),
|
|
3977
|
+
mentionTargetName,
|
|
3978
|
+
mentionOpenId: mentionTarget?.openId,
|
|
3979
|
+
mentionUserId: mentionTarget?.userId
|
|
3730
3980
|
});
|
|
3731
3981
|
return JSON.stringify({ ok: true, job });
|
|
3732
3982
|
}
|
|
@@ -3930,8 +4180,12 @@ async function runFeishuToolLoop(input2) {
|
|
|
3930
4180
|
}
|
|
3931
4181
|
const maxModelTurns = input2.maxModelTurns ?? DEFAULT_MAX_MODEL_TURNS;
|
|
3932
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;
|
|
3933
4187
|
const messages = [
|
|
3934
|
-
{ role: "system", content:
|
|
4188
|
+
{ role: "system", content: systemPrompt },
|
|
3935
4189
|
{ role: "user", content: `\u5F53\u524D\u65F6\u95F4\uFF1A${input2.now.toISOString()}
|
|
3936
4190
|
\u95EE\u9898\uFF1A${input2.question}` }
|
|
3937
4191
|
];
|
|
@@ -4043,8 +4297,13 @@ function getFeishuQuestionDecision(payload, config) {
|
|
|
4043
4297
|
var FeishuQuestionHandler = class {
|
|
4044
4298
|
constructor(options) {
|
|
4045
4299
|
this.options = options;
|
|
4300
|
+
this.memberResolver = options.memberResolver;
|
|
4046
4301
|
}
|
|
4047
4302
|
options;
|
|
4303
|
+
memberResolver;
|
|
4304
|
+
setMemberResolver(memberResolver) {
|
|
4305
|
+
this.memberResolver = memberResolver;
|
|
4306
|
+
}
|
|
4048
4307
|
async sendResponse(chatId, messageId, text) {
|
|
4049
4308
|
if (messageId && this.options.sender.replyTextToMessage) {
|
|
4050
4309
|
try {
|
|
@@ -4091,14 +4350,18 @@ var FeishuQuestionHandler = class {
|
|
|
4091
4350
|
const cronTools = createCronJobTools({
|
|
4092
4351
|
repository: new CronJobRepository(this.options.database),
|
|
4093
4352
|
chatId: decision.chatId,
|
|
4094
|
-
createdByOpenId: payload.event?.sender?.sender_id?.open_id
|
|
4353
|
+
createdByOpenId: payload.event?.sender?.sender_id?.open_id,
|
|
4354
|
+
memberResolver: this.memberResolver
|
|
4095
4355
|
});
|
|
4096
4356
|
const allTools = [...tools, ...cronTools];
|
|
4357
|
+
const memberRepository = this.options.memberRepository ?? new FeishuMemberRepository(this.options.database);
|
|
4358
|
+
const memberPrompt = formatFeishuMemberPrompt(memberRepository.listByChat(decision.chatId));
|
|
4097
4359
|
const answer = await runFeishuToolLoop({
|
|
4098
4360
|
question: decision.question,
|
|
4099
4361
|
now,
|
|
4100
4362
|
tools: allTools,
|
|
4101
|
-
model: this.options.model
|
|
4363
|
+
model: this.options.model,
|
|
4364
|
+
memberPrompt
|
|
4102
4365
|
});
|
|
4103
4366
|
qaLogs.create({
|
|
4104
4367
|
chatId: decision.chatId,
|
|
@@ -4151,6 +4414,15 @@ function extractImageKey(response) {
|
|
|
4151
4414
|
}
|
|
4152
4415
|
throw new Error("\u98DE\u4E66\u56FE\u7247\u4E0A\u4F20\u54CD\u5E94\u7F3A\u5C11 image_key\u3002");
|
|
4153
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
|
+
}
|
|
4154
4426
|
var FeishuMessageSender = class _FeishuMessageSender {
|
|
4155
4427
|
constructor(client) {
|
|
4156
4428
|
this.client = client;
|
|
@@ -4164,12 +4436,12 @@ var FeishuMessageSender = class _FeishuMessageSender {
|
|
|
4164
4436
|
});
|
|
4165
4437
|
return new _FeishuMessageSender(client);
|
|
4166
4438
|
}
|
|
4167
|
-
async sendTextToChat(chatId, text) {
|
|
4439
|
+
async sendTextToChat(chatId, text, options) {
|
|
4168
4440
|
const payload = {
|
|
4169
4441
|
data: {
|
|
4170
4442
|
receive_id: chatId,
|
|
4171
4443
|
msg_type: "text",
|
|
4172
|
-
content: JSON.stringify({ text })
|
|
4444
|
+
content: JSON.stringify({ text: formatTextWithMentions(text, options) })
|
|
4173
4445
|
},
|
|
4174
4446
|
params: {
|
|
4175
4447
|
receive_id_type: "chat_id"
|
|
@@ -4291,13 +4563,24 @@ function createFeishuEventDispatcher(options) {
|
|
|
4291
4563
|
return;
|
|
4292
4564
|
}
|
|
4293
4565
|
}
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
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
|
+
}
|
|
4301
4584
|
if (!result.accepted) {
|
|
4302
4585
|
console.log(`\u98DE\u4E66\u6D88\u606F\u672A\u5165\u5E93\uFF1A${result.reason}`);
|
|
4303
4586
|
return;
|
|
@@ -4388,10 +4671,20 @@ function createFeishuGateway(options) {
|
|
|
4388
4671
|
onReconnecting: () => console.log("\u98DE\u4E66\u957F\u8FDE\u63A5\u6B63\u5728\u91CD\u8FDE\u3002"),
|
|
4389
4672
|
onReconnected: () => console.log("\u98DE\u4E66\u957F\u8FDE\u63A5\u5DF2\u91CD\u8FDE\u3002")
|
|
4390
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);
|
|
4391
4683
|
const eventDispatcher = createFeishuEventDispatcher({
|
|
4392
4684
|
config: options.config,
|
|
4393
4685
|
secrets: options.secrets,
|
|
4394
4686
|
ingestor: options.ingestor,
|
|
4687
|
+
memberResolver,
|
|
4395
4688
|
questionHandler: options.questionHandler,
|
|
4396
4689
|
resourceDownloader: options.resourceDownloader,
|
|
4397
4690
|
attachmentVectorIndexer: options.attachmentVectorIndexer,
|
|
@@ -4411,7 +4704,7 @@ function createFeishuGateway(options) {
|
|
|
4411
4704
|
}) : void 0);
|
|
4412
4705
|
const cronJobScheduler = options.cronJobScheduler ?? (options.cronJobProcessor ? createCronJobScheduler({
|
|
4413
4706
|
repository: new CronJobRepository(options.cronJobProcessor.database),
|
|
4414
|
-
sendTextToChat: (chatId, text) => options.cronJobProcessor.sender.sendTextToChat(chatId, text),
|
|
4707
|
+
sendTextToChat: (chatId, text, sendOptions) => options.cronJobProcessor.sender.sendTextToChat(chatId, text, sendOptions),
|
|
4415
4708
|
sendImageToChat: options.cronJobProcessor.sender.sendImageToChat ? (chatId, imageFileName) => options.cronJobProcessor.sender.sendImageToChat(
|
|
4416
4709
|
chatId,
|
|
4417
4710
|
resolveFeishuImagePath(options.config, imageFileName)
|
|
@@ -4425,7 +4718,16 @@ function createFeishuGateway(options) {
|
|
|
4425
4718
|
scope: { platform: "feishu", platformChatId: job.chatId }
|
|
4426
4719
|
});
|
|
4427
4720
|
try {
|
|
4428
|
-
|
|
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
|
+
});
|
|
4429
4731
|
} finally {
|
|
4430
4732
|
close();
|
|
4431
4733
|
}
|
|
@@ -4783,7 +5085,9 @@ function normalizeFeishuReceiveMessageEvent(payload) {
|
|
|
4783
5085
|
if (!text) {
|
|
4784
5086
|
return null;
|
|
4785
5087
|
}
|
|
4786
|
-
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";
|
|
4787
5091
|
return {
|
|
4788
5092
|
platform: "feishu",
|
|
4789
5093
|
platformChatId: message.chat_id,
|
|
@@ -4798,12 +5102,24 @@ function normalizeFeishuReceiveMessageEvent(payload) {
|
|
|
4798
5102
|
platform: "feishu",
|
|
4799
5103
|
raw: payload,
|
|
4800
5104
|
content,
|
|
5105
|
+
sender: {
|
|
5106
|
+
...senderOpenId ? { openId: senderOpenId } : {},
|
|
5107
|
+
...senderUserId ? { userId: senderUserId } : {}
|
|
5108
|
+
},
|
|
4801
5109
|
attachment: extractFeishuAttachment(messageType, content)
|
|
4802
5110
|
}
|
|
4803
5111
|
};
|
|
4804
5112
|
}
|
|
4805
5113
|
|
|
4806
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
|
+
}
|
|
4807
5123
|
function extractAttachment(message) {
|
|
4808
5124
|
const raw = message.rawPayload;
|
|
4809
5125
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -4823,14 +5139,16 @@ function isMultimodalReady(config, secrets) {
|
|
|
4823
5139
|
return Boolean(config.multimodal.baseUrl && config.multimodal.model && secrets.multimodal.apiKey);
|
|
4824
5140
|
}
|
|
4825
5141
|
var GatewayIngestor = class {
|
|
4826
|
-
messages;
|
|
4827
|
-
jobs;
|
|
4828
|
-
imageTasks;
|
|
4829
5142
|
constructor(database) {
|
|
5143
|
+
this.database = database;
|
|
4830
5144
|
this.messages = new MessageRepository(database);
|
|
4831
5145
|
this.jobs = new FileJobRepository(database);
|
|
4832
5146
|
this.imageTasks = new ImageMultimodalTaskRepository(database);
|
|
4833
5147
|
}
|
|
5148
|
+
database;
|
|
5149
|
+
messages;
|
|
5150
|
+
jobs;
|
|
5151
|
+
imageTasks;
|
|
4834
5152
|
ingestFeishuEvent(payload) {
|
|
4835
5153
|
const normalized = normalizeFeishuReceiveMessageEvent(payload);
|
|
4836
5154
|
if (!normalized) {
|
|
@@ -4848,8 +5166,28 @@ var GatewayIngestor = class {
|
|
|
4848
5166
|
duplicate
|
|
4849
5167
|
};
|
|
4850
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
|
+
}
|
|
4851
5189
|
async ingestFeishuEventAndDownloadAttachments(input2) {
|
|
4852
|
-
const result = this.ingestFeishuEvent(input2.payload);
|
|
5190
|
+
const result = input2.memberResolver ? await this.ingestFeishuEventWithMembers({ payload: input2.payload, memberResolver: input2.memberResolver }) : this.ingestFeishuEvent(input2.payload);
|
|
4853
5191
|
if (!result.accepted || !result.messageId || !result.message || result.duplicate) {
|
|
4854
5192
|
return result;
|
|
4855
5193
|
}
|
|
@@ -5104,6 +5442,9 @@ function createMultimodalModel(config, secrets) {
|
|
|
5104
5442
|
});
|
|
5105
5443
|
}
|
|
5106
5444
|
|
|
5445
|
+
// src/cli.ts
|
|
5446
|
+
import * as lark4 from "@larksuiteoapi/node-sdk";
|
|
5447
|
+
|
|
5107
5448
|
// src/rag/answer.ts
|
|
5108
5449
|
var DEFAULT_MAX_EVIDENCE_BLOCKS = 8;
|
|
5109
5450
|
var DEFAULT_MAX_CHARS_PER_BLOCK = 1200;
|
|
@@ -6201,6 +6542,14 @@ async function startGatewayForegroundCommand() {
|
|
|
6201
6542
|
const database = openDatabase(config);
|
|
6202
6543
|
const chatModel = createChatModel(config, secrets);
|
|
6203
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
|
+
});
|
|
6204
6553
|
const vectorStore = hasEmbeddingConfig(config, secrets) ? new SqliteVectorStore(database, { model: config.embedding.model }) : null;
|
|
6205
6554
|
const gatewayRuntime = createFeishuGateway({
|
|
6206
6555
|
config,
|
|
@@ -6233,6 +6582,7 @@ async function startGatewayForegroundCommand() {
|
|
|
6233
6582
|
config,
|
|
6234
6583
|
secrets,
|
|
6235
6584
|
database,
|
|
6585
|
+
memberResolver,
|
|
6236
6586
|
sender,
|
|
6237
6587
|
model: chatModel
|
|
6238
6588
|
})
|