chattercatcher 0.1.19 → 0.1.21
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 +1118 -439
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +226 -129
- package/dist/index.js +1025 -336
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs14 from "fs/promises";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "chattercatcher",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.21",
|
|
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",
|
|
@@ -141,6 +141,12 @@ var appSecretsSchema = z.object({
|
|
|
141
141
|
z.object({
|
|
142
142
|
apiKey: z.string().default("")
|
|
143
143
|
})
|
|
144
|
+
),
|
|
145
|
+
web: z.preprocess(
|
|
146
|
+
(value) => value ?? {},
|
|
147
|
+
z.object({
|
|
148
|
+
actionToken: z.string().default("")
|
|
149
|
+
})
|
|
144
150
|
)
|
|
145
151
|
});
|
|
146
152
|
function createDefaultConfig() {
|
|
@@ -160,7 +166,8 @@ function createDefaultSecrets() {
|
|
|
160
166
|
feishu: {},
|
|
161
167
|
llm: {},
|
|
162
168
|
embedding: {},
|
|
163
|
-
multimodal: {}
|
|
169
|
+
multimodal: {},
|
|
170
|
+
web: {}
|
|
164
171
|
});
|
|
165
172
|
}
|
|
166
173
|
|
|
@@ -529,6 +536,23 @@ function migrateDatabase(database) {
|
|
|
529
536
|
);
|
|
530
537
|
|
|
531
538
|
CREATE INDEX IF NOT EXISTS image_multimodal_tasks_status_idx ON image_multimodal_tasks(status, updated_at);
|
|
539
|
+
|
|
540
|
+
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
541
|
+
id TEXT PRIMARY KEY,
|
|
542
|
+
chat_id TEXT NOT NULL,
|
|
543
|
+
created_by_open_id TEXT,
|
|
544
|
+
schedule TEXT NOT NULL,
|
|
545
|
+
prompt TEXT NOT NULL,
|
|
546
|
+
status TEXT NOT NULL CHECK(status IN ('active','deleted')),
|
|
547
|
+
last_run_at TEXT,
|
|
548
|
+
next_run_at TEXT NOT NULL,
|
|
549
|
+
last_error TEXT,
|
|
550
|
+
created_at TEXT NOT NULL,
|
|
551
|
+
updated_at TEXT NOT NULL
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
CREATE INDEX IF NOT EXISTS cron_jobs_chat_status_idx ON cron_jobs(chat_id, status, updated_at);
|
|
555
|
+
CREATE INDEX IF NOT EXISTS cron_jobs_due_idx ON cron_jobs(status, next_run_at);
|
|
532
556
|
`);
|
|
533
557
|
}
|
|
534
558
|
|
|
@@ -959,7 +983,8 @@ function toOpenAIMessage(message) {
|
|
|
959
983
|
arguments: JSON.stringify(toolCall.input)
|
|
960
984
|
}
|
|
961
985
|
}))
|
|
962
|
-
} : {}
|
|
986
|
+
} : {},
|
|
987
|
+
...message.reasoningContent ? { reasoning_content: message.reasoningContent } : {}
|
|
963
988
|
};
|
|
964
989
|
}
|
|
965
990
|
function toOpenAITool(tool) {
|
|
@@ -1005,7 +1030,8 @@ var OpenAICompatibleChatModel = class {
|
|
|
1005
1030
|
throw new Error(`LLM \u8BF7\u6C42\u5931\u8D25\uFF1A${response.status} ${body}`);
|
|
1006
1031
|
}
|
|
1007
1032
|
const data2 = await response.json();
|
|
1008
|
-
const
|
|
1033
|
+
const message = data2.choices?.[0]?.message;
|
|
1034
|
+
const content = message?.content?.trim();
|
|
1009
1035
|
if (!content) {
|
|
1010
1036
|
throw new Error("LLM \u8FD4\u56DE\u4E3A\u7A7A\u3002");
|
|
1011
1037
|
}
|
|
@@ -1037,7 +1063,8 @@ var OpenAICompatibleChatModel = class {
|
|
|
1037
1063
|
const message = data2.choices?.[0]?.message;
|
|
1038
1064
|
return {
|
|
1039
1065
|
content: message?.content ?? "",
|
|
1040
|
-
toolCalls: parseToolCalls(message)
|
|
1066
|
+
toolCalls: parseToolCalls(message),
|
|
1067
|
+
reasoningContent: message?.reasoning_content ?? void 0
|
|
1041
1068
|
};
|
|
1042
1069
|
}
|
|
1043
1070
|
};
|
|
@@ -1148,6 +1175,22 @@ function buildSearchTerms(query) {
|
|
|
1148
1175
|
}
|
|
1149
1176
|
return [trimmed];
|
|
1150
1177
|
}
|
|
1178
|
+
function buildScopeWhere(scope) {
|
|
1179
|
+
const clauses = [];
|
|
1180
|
+
const params = [];
|
|
1181
|
+
if (scope?.platform) {
|
|
1182
|
+
clauses.push("m.platform = ?");
|
|
1183
|
+
params.push(scope.platform);
|
|
1184
|
+
}
|
|
1185
|
+
if (scope?.platformChatId) {
|
|
1186
|
+
clauses.push("c.platform_chat_id = ?");
|
|
1187
|
+
params.push(scope.platformChatId);
|
|
1188
|
+
}
|
|
1189
|
+
return {
|
|
1190
|
+
where: clauses.length > 0 ? `AND ${clauses.join(" AND ")}` : "",
|
|
1191
|
+
params
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1151
1194
|
function parseRawPayload(value) {
|
|
1152
1195
|
try {
|
|
1153
1196
|
const parsed = JSON.parse(value);
|
|
@@ -1353,6 +1396,7 @@ var MessageRepository = class {
|
|
|
1353
1396
|
const ftsQuery = escapeFtsQuery(query);
|
|
1354
1397
|
const excludedIds = options.excludeMessageIds ?? [];
|
|
1355
1398
|
const excludedWhere = excludedIds.length > 0 ? `AND fts.message_id NOT IN (${excludedIds.map(() => "?").join(", ")})` : "";
|
|
1399
|
+
const scope = buildScopeWhere(options.scope);
|
|
1356
1400
|
const ftsResults = this.database.prepare(
|
|
1357
1401
|
`
|
|
1358
1402
|
SELECT
|
|
@@ -1371,10 +1415,11 @@ var MessageRepository = class {
|
|
|
1371
1415
|
JOIN chats c ON c.id = m.chat_id
|
|
1372
1416
|
WHERE message_chunks_fts MATCH ?
|
|
1373
1417
|
${excludedWhere}
|
|
1418
|
+
${scope.where}
|
|
1374
1419
|
ORDER BY bm25(message_chunks_fts)
|
|
1375
1420
|
LIMIT ?
|
|
1376
1421
|
`
|
|
1377
|
-
).all(ftsQuery, ...excludedIds, limit);
|
|
1422
|
+
).all(ftsQuery, ...excludedIds, ...scope.params, limit);
|
|
1378
1423
|
if (ftsResults.length > 0) {
|
|
1379
1424
|
return ftsResults;
|
|
1380
1425
|
}
|
|
@@ -1402,10 +1447,11 @@ var MessageRepository = class {
|
|
|
1402
1447
|
JOIN chats c ON c.id = m.chat_id
|
|
1403
1448
|
WHERE (${where})
|
|
1404
1449
|
${likeExcludedWhere}
|
|
1450
|
+
${scope.where}
|
|
1405
1451
|
ORDER BY m.sent_at DESC
|
|
1406
1452
|
LIMIT ?
|
|
1407
1453
|
`
|
|
1408
|
-
).all(...params, ...excludedIds, limit);
|
|
1454
|
+
).all(...params, ...excludedIds, ...scope.params, limit);
|
|
1409
1455
|
}
|
|
1410
1456
|
getChatCount() {
|
|
1411
1457
|
return this.database.prepare("SELECT COUNT(*) AS count FROM chats").get().count;
|
|
@@ -1505,6 +1551,22 @@ function toMillis(value) {
|
|
|
1505
1551
|
const time = Date.parse(value);
|
|
1506
1552
|
return Number.isFinite(time) ? time : 0;
|
|
1507
1553
|
}
|
|
1554
|
+
function buildScopeWhere2(scope) {
|
|
1555
|
+
const clauses = [];
|
|
1556
|
+
const params = [];
|
|
1557
|
+
if (scope?.platform) {
|
|
1558
|
+
clauses.push("c.platform = ?");
|
|
1559
|
+
params.push(scope.platform);
|
|
1560
|
+
}
|
|
1561
|
+
if (scope?.platformChatId) {
|
|
1562
|
+
clauses.push("c.platform_chat_id = ?");
|
|
1563
|
+
params.push(scope.platformChatId);
|
|
1564
|
+
}
|
|
1565
|
+
return {
|
|
1566
|
+
where: clauses.length > 0 ? `AND ${clauses.join(" AND ")}` : "",
|
|
1567
|
+
params
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1508
1570
|
var EpisodeRepository = class {
|
|
1509
1571
|
constructor(database) {
|
|
1510
1572
|
this.database = database;
|
|
@@ -1687,8 +1749,9 @@ var EpisodeRepository = class {
|
|
|
1687
1749
|
`
|
|
1688
1750
|
).all(limit);
|
|
1689
1751
|
}
|
|
1690
|
-
searchEpisodes(query, limit = 8) {
|
|
1752
|
+
searchEpisodes(query, limit = 8, scope) {
|
|
1691
1753
|
const ftsQuery = escapeFtsQuery2(query);
|
|
1754
|
+
const scopeWhere = buildScopeWhere2(scope);
|
|
1692
1755
|
return this.database.prepare(
|
|
1693
1756
|
`
|
|
1694
1757
|
SELECT
|
|
@@ -1716,11 +1779,12 @@ var EpisodeRepository = class {
|
|
|
1716
1779
|
JOIN memory_episodes e ON e.id = fts.episode_id
|
|
1717
1780
|
JOIN chats c ON c.id = e.chat_id
|
|
1718
1781
|
WHERE memory_episodes_fts MATCH ?
|
|
1782
|
+
${scopeWhere.where}
|
|
1719
1783
|
GROUP BY e.id
|
|
1720
1784
|
ORDER BY e.ended_at DESC
|
|
1721
1785
|
LIMIT ?
|
|
1722
1786
|
`
|
|
1723
|
-
).all(ftsQuery, limit).map((row) => {
|
|
1787
|
+
).all(ftsQuery, ...scopeWhere.params, limit).map((row) => {
|
|
1724
1788
|
const item = row;
|
|
1725
1789
|
return {
|
|
1726
1790
|
...item,
|
|
@@ -1750,8 +1814,8 @@ var EpisodeFtsRetriever = class {
|
|
|
1750
1814
|
this.episodes = episodes;
|
|
1751
1815
|
}
|
|
1752
1816
|
episodes;
|
|
1753
|
-
async retrieve(question) {
|
|
1754
|
-
return this.episodes.searchEpisodes(question, 8).map(toEpisodeEvidence);
|
|
1817
|
+
async retrieve(question, scope) {
|
|
1818
|
+
return this.episodes.searchEpisodes(question, 8, scope).map(toEpisodeEvidence);
|
|
1755
1819
|
}
|
|
1756
1820
|
};
|
|
1757
1821
|
|
|
@@ -1777,8 +1841,9 @@ var HybridRetriever = class {
|
|
|
1777
1841
|
}
|
|
1778
1842
|
retrievers;
|
|
1779
1843
|
options;
|
|
1780
|
-
async retrieve(question) {
|
|
1781
|
-
const
|
|
1844
|
+
async retrieve(question, scope) {
|
|
1845
|
+
const effectiveScope = scope ?? this.options.scope;
|
|
1846
|
+
const results = await Promise.all(this.retrievers.map((retriever) => retriever.retrieve(question, effectiveScope)));
|
|
1782
1847
|
const merged = /* @__PURE__ */ new Map();
|
|
1783
1848
|
for (const evidenceList of results) {
|
|
1784
1849
|
for (const evidence of evidenceList) {
|
|
@@ -1819,9 +1884,10 @@ var MessageFtsRetriever = class {
|
|
|
1819
1884
|
}
|
|
1820
1885
|
messages;
|
|
1821
1886
|
options;
|
|
1822
|
-
async retrieve(question) {
|
|
1887
|
+
async retrieve(question, scope) {
|
|
1823
1888
|
const results = this.messages.searchMessages(question, 8, {
|
|
1824
|
-
excludeMessageIds: this.options.excludeMessageIds
|
|
1889
|
+
excludeMessageIds: this.options.excludeMessageIds,
|
|
1890
|
+
scope
|
|
1825
1891
|
});
|
|
1826
1892
|
return results.map((result) => ({
|
|
1827
1893
|
id: result.chunkId,
|
|
@@ -1856,17 +1922,17 @@ function parseSearchInput(input2) {
|
|
|
1856
1922
|
const limit = Math.min(12, Math.max(1, Math.floor(numericLimit)));
|
|
1857
1923
|
return { query, limit };
|
|
1858
1924
|
}
|
|
1859
|
-
async function runRetriever(retriever, input2) {
|
|
1925
|
+
async function runRetriever(retriever, input2, scope) {
|
|
1860
1926
|
const { query, limit } = parseSearchInput(input2);
|
|
1861
|
-
const results = await retriever.retrieve(query);
|
|
1927
|
+
const results = await retriever.retrieve(query, scope);
|
|
1862
1928
|
return results.slice(0, limit);
|
|
1863
1929
|
}
|
|
1864
|
-
function createSearchTool(name, description, retriever) {
|
|
1930
|
+
function createSearchTool(name, description, retriever, scope) {
|
|
1865
1931
|
return {
|
|
1866
1932
|
name,
|
|
1867
1933
|
description,
|
|
1868
1934
|
inputSchema: searchInputSchema,
|
|
1869
|
-
execute: (input2) => runRetriever(retriever, input2)
|
|
1935
|
+
execute: (input2) => runRetriever(retriever, input2, scope)
|
|
1870
1936
|
};
|
|
1871
1937
|
}
|
|
1872
1938
|
function createRagSearchTools(input2) {
|
|
@@ -1874,17 +1940,20 @@ function createRagSearchTools(input2) {
|
|
|
1874
1940
|
createSearchTool(
|
|
1875
1941
|
"hybrid_search",
|
|
1876
1942
|
"Search across all indexed RAG evidence using the default hybrid retrieval strategy.",
|
|
1877
|
-
input2.hybrid
|
|
1943
|
+
input2.hybrid,
|
|
1944
|
+
input2.scope
|
|
1878
1945
|
),
|
|
1879
1946
|
createSearchTool(
|
|
1880
1947
|
"search_messages",
|
|
1881
1948
|
"Search chat messages only when the answer likely depends on message-level evidence.",
|
|
1882
|
-
input2.messages
|
|
1949
|
+
input2.messages,
|
|
1950
|
+
input2.scope
|
|
1883
1951
|
),
|
|
1884
1952
|
createSearchTool(
|
|
1885
1953
|
"search_episodes",
|
|
1886
1954
|
"Search episode summaries only when the answer likely depends on longer-running context.",
|
|
1887
|
-
input2.episodes
|
|
1955
|
+
input2.episodes,
|
|
1956
|
+
input2.scope
|
|
1888
1957
|
)
|
|
1889
1958
|
];
|
|
1890
1959
|
if (input2.semantic) {
|
|
@@ -1892,7 +1961,8 @@ function createRagSearchTools(input2) {
|
|
|
1892
1961
|
createSearchTool(
|
|
1893
1962
|
"semantic_search",
|
|
1894
1963
|
"Search semantic vector evidence only when broader conceptual recall is needed.",
|
|
1895
|
-
input2.semantic
|
|
1964
|
+
input2.semantic,
|
|
1965
|
+
input2.scope
|
|
1896
1966
|
)
|
|
1897
1967
|
);
|
|
1898
1968
|
}
|
|
@@ -1937,6 +2007,22 @@ function toEvidenceSource2(row) {
|
|
|
1937
2007
|
timestamp: row.sentAt
|
|
1938
2008
|
};
|
|
1939
2009
|
}
|
|
2010
|
+
function buildScopeWhere3(scope) {
|
|
2011
|
+
const clauses = [];
|
|
2012
|
+
const params = [];
|
|
2013
|
+
if (scope?.platform) {
|
|
2014
|
+
clauses.push("m.platform = ?");
|
|
2015
|
+
params.push(scope.platform);
|
|
2016
|
+
}
|
|
2017
|
+
if (scope?.platformChatId) {
|
|
2018
|
+
clauses.push("c.platform_chat_id = ?");
|
|
2019
|
+
params.push(scope.platformChatId);
|
|
2020
|
+
}
|
|
2021
|
+
return {
|
|
2022
|
+
where: clauses.length > 0 ? `AND ${clauses.join(" AND ")}` : "",
|
|
2023
|
+
params
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
1940
2026
|
var SqliteVectorStore = class {
|
|
1941
2027
|
constructor(database, options) {
|
|
1942
2028
|
this.database = database;
|
|
@@ -1971,10 +2057,11 @@ var SqliteVectorStore = class {
|
|
|
1971
2057
|
});
|
|
1972
2058
|
transaction(records);
|
|
1973
2059
|
}
|
|
1974
|
-
async search(vector, limit) {
|
|
2060
|
+
async search(vector, limit, scope) {
|
|
1975
2061
|
if (limit <= 0) {
|
|
1976
2062
|
return [];
|
|
1977
2063
|
}
|
|
2064
|
+
const scopeWhere = buildScopeWhere3(scope);
|
|
1978
2065
|
const rows = this.database.prepare(
|
|
1979
2066
|
`
|
|
1980
2067
|
SELECT
|
|
@@ -1989,8 +2076,9 @@ var SqliteVectorStore = class {
|
|
|
1989
2076
|
JOIN messages m ON m.id = mc.message_id
|
|
1990
2077
|
JOIN chats c ON c.id = m.chat_id
|
|
1991
2078
|
WHERE e.model = ?
|
|
2079
|
+
${scopeWhere.where}
|
|
1992
2080
|
`
|
|
1993
|
-
).all(this.options.model);
|
|
2081
|
+
).all(this.options.model, ...scopeWhere.params);
|
|
1994
2082
|
return rows.flatMap((row) => {
|
|
1995
2083
|
const storedVector = parseEmbeddingJson(row.embeddingJson);
|
|
1996
2084
|
if (storedVector.length === 0) {
|
|
@@ -2022,9 +2110,9 @@ var VectorRetriever = class {
|
|
|
2022
2110
|
embedding;
|
|
2023
2111
|
store;
|
|
2024
2112
|
limit;
|
|
2025
|
-
async retrieve(question) {
|
|
2113
|
+
async retrieve(question, scope) {
|
|
2026
2114
|
const vector = await this.embedding.embed(question);
|
|
2027
|
-
return this.store.search(vector, this.limit);
|
|
2115
|
+
return this.store.search(vector, this.limit, scope);
|
|
2028
2116
|
}
|
|
2029
2117
|
};
|
|
2030
2118
|
|
|
@@ -2045,7 +2133,7 @@ async function createHybridRetriever(input2) {
|
|
|
2045
2133
|
retrievers.push(new VectorRetriever(createEmbeddingModel(input2.config, input2.secrets), vectorStore));
|
|
2046
2134
|
}
|
|
2047
2135
|
return {
|
|
2048
|
-
retriever: new HybridRetriever(retrievers),
|
|
2136
|
+
retriever: new HybridRetriever(retrievers, { scope: input2.scope }),
|
|
2049
2137
|
close: () => {
|
|
2050
2138
|
for (const closer of closers) {
|
|
2051
2139
|
closer();
|
|
@@ -2062,7 +2150,7 @@ async function createAgenticRagSearchTools(input2) {
|
|
|
2062
2150
|
) : void 0;
|
|
2063
2151
|
const hybrid = new HybridRetriever(semantic ? [episodes, messages, semantic] : [episodes, messages]);
|
|
2064
2152
|
return {
|
|
2065
|
-
tools: createRagSearchTools({ hybrid, messages, episodes, semantic }),
|
|
2153
|
+
tools: createRagSearchTools({ hybrid, messages, episodes, semantic, scope: input2.scope }),
|
|
2066
2154
|
close: () => {
|
|
2067
2155
|
}
|
|
2068
2156
|
};
|
|
@@ -2635,50 +2723,35 @@ async function ensureFeishuBotOpenId(config, secrets, options = {}) {
|
|
|
2635
2723
|
// src/feishu/gateway.ts
|
|
2636
2724
|
import * as lark2 from "@larksuiteoapi/node-sdk";
|
|
2637
2725
|
|
|
2638
|
-
// src/
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
} finally {
|
|
2658
|
-
running = false;
|
|
2726
|
+
// src/cron/jobs.ts
|
|
2727
|
+
import crypto4 from "crypto";
|
|
2728
|
+
|
|
2729
|
+
// src/cron/schedule.ts
|
|
2730
|
+
function isValidCronSchedule(schedule) {
|
|
2731
|
+
return parseCronSchedule(schedule) !== null;
|
|
2732
|
+
}
|
|
2733
|
+
function getNextCronRun(schedule, after) {
|
|
2734
|
+
const parsed = parseCronSchedule(schedule);
|
|
2735
|
+
if (!parsed) {
|
|
2736
|
+
return null;
|
|
2737
|
+
}
|
|
2738
|
+
const candidate = new Date(after);
|
|
2739
|
+
candidate.setSeconds(0, 0);
|
|
2740
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
2741
|
+
const maxMinutes = 5 * 366 * 24 * 60;
|
|
2742
|
+
for (let i = 0; i < maxMinutes; i += 1) {
|
|
2743
|
+
if (matchesParsedSchedule(parsed, candidate)) {
|
|
2744
|
+
return new Date(candidate);
|
|
2659
2745
|
}
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
if (!parsed || timer) {
|
|
2664
|
-
return;
|
|
2665
|
-
}
|
|
2666
|
-
timer = setIntervalFn(() => {
|
|
2667
|
-
void runDueNow();
|
|
2668
|
-
}, 6e4);
|
|
2669
|
-
},
|
|
2670
|
-
stop() {
|
|
2671
|
-
if (!timer) {
|
|
2672
|
-
return;
|
|
2673
|
-
}
|
|
2674
|
-
clearIntervalFn(timer);
|
|
2675
|
-
timer = void 0;
|
|
2676
|
-
},
|
|
2677
|
-
runDueNow
|
|
2678
|
-
};
|
|
2746
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
2747
|
+
}
|
|
2748
|
+
return null;
|
|
2679
2749
|
}
|
|
2680
2750
|
function matchesParsedSchedule(schedule, date) {
|
|
2681
|
-
|
|
2751
|
+
const dayOfMonthMatches = schedule.dayOfMonth.matches(date.getDate());
|
|
2752
|
+
const dayOfWeekMatches = schedule.dayOfWeek.matches(date.getDay());
|
|
2753
|
+
const dayMatches = schedule.dayOfMonth.wildcard || schedule.dayOfWeek.wildcard ? dayOfMonthMatches && dayOfWeekMatches : dayOfMonthMatches || dayOfWeekMatches;
|
|
2754
|
+
return schedule.minute.matches(date.getMinutes()) && schedule.hour.matches(date.getHours()) && dayMatches && schedule.month.matches(date.getMonth() + 1);
|
|
2682
2755
|
}
|
|
2683
2756
|
function parseCronSchedule(schedule) {
|
|
2684
2757
|
const fields = schedule.trim().split(/\s+/);
|
|
@@ -2697,7 +2770,7 @@ function parseCronSchedule(schedule) {
|
|
|
2697
2770
|
}
|
|
2698
2771
|
function parseMinuteField(field) {
|
|
2699
2772
|
if (field === "*") {
|
|
2700
|
-
return () => true;
|
|
2773
|
+
return { wildcard: true, matches: () => true };
|
|
2701
2774
|
}
|
|
2702
2775
|
const stepMatch = /^\*\/(\d+)$/.exec(field);
|
|
2703
2776
|
if (stepMatch) {
|
|
@@ -2705,7 +2778,7 @@ function parseMinuteField(field) {
|
|
|
2705
2778
|
if (!Number.isInteger(step) || step <= 0 || step > 59) {
|
|
2706
2779
|
return null;
|
|
2707
2780
|
}
|
|
2708
|
-
return (value) => value % step === 0;
|
|
2781
|
+
return { wildcard: false, matches: (value) => value % step === 0 };
|
|
2709
2782
|
}
|
|
2710
2783
|
if (field.includes(",")) {
|
|
2711
2784
|
const values = field.split(",").map((part) => parseExactNumber(part, 0, 59));
|
|
@@ -2713,23 +2786,23 @@ function parseMinuteField(field) {
|
|
|
2713
2786
|
return null;
|
|
2714
2787
|
}
|
|
2715
2788
|
const allowed = new Set(values);
|
|
2716
|
-
return (value) => allowed.has(value);
|
|
2789
|
+
return { wildcard: false, matches: (value) => allowed.has(value) };
|
|
2717
2790
|
}
|
|
2718
2791
|
const exact = parseExactNumber(field, 0, 59);
|
|
2719
2792
|
if (exact === null) {
|
|
2720
2793
|
return null;
|
|
2721
2794
|
}
|
|
2722
|
-
return (value) => value === exact;
|
|
2795
|
+
return { wildcard: false, matches: (value) => value === exact };
|
|
2723
2796
|
}
|
|
2724
2797
|
function parseExactOrWildcardField(field, min, max) {
|
|
2725
2798
|
if (field === "*") {
|
|
2726
|
-
return () => true;
|
|
2799
|
+
return { wildcard: true, matches: () => true };
|
|
2727
2800
|
}
|
|
2728
2801
|
const exact = parseExactNumber(field, min, max);
|
|
2729
2802
|
if (exact === null) {
|
|
2730
2803
|
return null;
|
|
2731
2804
|
}
|
|
2732
|
-
return (value) => value === exact;
|
|
2805
|
+
return { wildcard: false, matches: (value) => value === exact };
|
|
2733
2806
|
}
|
|
2734
2807
|
function parseExactNumber(field, min, max) {
|
|
2735
2808
|
if (!/^\d+$/.test(field)) {
|
|
@@ -2742,108 +2815,529 @@ function parseExactNumber(field, min, max) {
|
|
|
2742
2815
|
return value;
|
|
2743
2816
|
}
|
|
2744
2817
|
|
|
2745
|
-
// src/
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2818
|
+
// src/cron/jobs.ts
|
|
2819
|
+
var CronJobRepository = class {
|
|
2820
|
+
constructor(database, options = {}) {
|
|
2821
|
+
this.database = database;
|
|
2822
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2750
2823
|
}
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
const
|
|
2755
|
-
|
|
2756
|
-
|
|
2824
|
+
database;
|
|
2825
|
+
now;
|
|
2826
|
+
create(input2) {
|
|
2827
|
+
const schedule = input2.schedule.trim();
|
|
2828
|
+
const prompt = input2.prompt.trim();
|
|
2829
|
+
if (!isValidCronSchedule(schedule)) {
|
|
2830
|
+
throw new Error("cron \u8868\u8FBE\u5F0F\u65E0\u6548\u3002");
|
|
2757
2831
|
}
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2832
|
+
if (!prompt) {
|
|
2833
|
+
throw new Error("\u5B9A\u65F6\u4EFB\u52A1 prompt \u4E0D\u80FD\u4E3A\u7A7A\u3002");
|
|
2834
|
+
}
|
|
2835
|
+
const now = this.now();
|
|
2836
|
+
const nextRunAt = getNextCronRun(schedule, now);
|
|
2837
|
+
if (!nextRunAt) {
|
|
2838
|
+
throw new Error("\u65E0\u6CD5\u8BA1\u7B97\u4E0B\u4E00\u6B21\u6267\u884C\u65F6\u95F4\u3002");
|
|
2839
|
+
}
|
|
2840
|
+
const record = {
|
|
2841
|
+
id: crypto4.randomUUID(),
|
|
2842
|
+
chatId: input2.chatId,
|
|
2843
|
+
createdByOpenId: input2.createdByOpenId,
|
|
2844
|
+
schedule,
|
|
2845
|
+
prompt,
|
|
2846
|
+
status: "active",
|
|
2847
|
+
nextRunAt: nextRunAt.toISOString(),
|
|
2848
|
+
createdAt: now.toISOString(),
|
|
2849
|
+
updatedAt: now.toISOString()
|
|
2850
|
+
};
|
|
2851
|
+
this.database.prepare(
|
|
2852
|
+
`
|
|
2853
|
+
INSERT INTO cron_jobs (
|
|
2854
|
+
id, chat_id, created_by_open_id, schedule, prompt, status,
|
|
2855
|
+
last_run_at, next_run_at, last_error, created_at, updated_at
|
|
2856
|
+
)
|
|
2857
|
+
VALUES (
|
|
2858
|
+
@id, @chatId, @createdByOpenId, @schedule, @prompt, @status,
|
|
2859
|
+
NULL, @nextRunAt, NULL, @createdAt, @updatedAt
|
|
2860
|
+
)
|
|
2861
|
+
`
|
|
2862
|
+
).run(record);
|
|
2863
|
+
return record;
|
|
2864
|
+
}
|
|
2865
|
+
get(id) {
|
|
2866
|
+
return this.listByWhere("WHERE id = ?", [id], 1)[0] ?? null;
|
|
2867
|
+
}
|
|
2868
|
+
list(limit = 100) {
|
|
2869
|
+
return this.listByWhere("", [], limit);
|
|
2870
|
+
}
|
|
2871
|
+
listByChat(chatId, limit = 50) {
|
|
2872
|
+
return this.listByWhere(
|
|
2873
|
+
"WHERE chat_id = ? AND status = 'active'",
|
|
2874
|
+
[chatId],
|
|
2875
|
+
limit
|
|
2876
|
+
);
|
|
2877
|
+
}
|
|
2878
|
+
listDue(now, limit = 20) {
|
|
2879
|
+
const rows = this.database.prepare(
|
|
2880
|
+
`
|
|
2881
|
+
SELECT
|
|
2882
|
+
id,
|
|
2883
|
+
chat_id AS chatId,
|
|
2884
|
+
created_by_open_id AS createdByOpenId,
|
|
2885
|
+
schedule,
|
|
2886
|
+
prompt,
|
|
2887
|
+
status,
|
|
2888
|
+
last_run_at AS lastRunAt,
|
|
2889
|
+
next_run_at AS nextRunAt,
|
|
2890
|
+
last_error AS lastError,
|
|
2891
|
+
created_at AS createdAt,
|
|
2892
|
+
updated_at AS updatedAt
|
|
2893
|
+
FROM cron_jobs
|
|
2894
|
+
WHERE status = 'active' AND next_run_at <= ?
|
|
2895
|
+
ORDER BY next_run_at ASC, updated_at ASC
|
|
2896
|
+
LIMIT ?
|
|
2897
|
+
`
|
|
2898
|
+
).all(now.toISOString(), limit);
|
|
2899
|
+
return rows.map((row) => ({
|
|
2900
|
+
id: row.id,
|
|
2901
|
+
chatId: row.chatId,
|
|
2902
|
+
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
2903
|
+
schedule: row.schedule,
|
|
2904
|
+
prompt: row.prompt,
|
|
2905
|
+
status: row.status,
|
|
2906
|
+
lastRunAt: row.lastRunAt ?? void 0,
|
|
2907
|
+
nextRunAt: row.nextRunAt,
|
|
2908
|
+
lastError: row.lastError ?? void 0,
|
|
2909
|
+
createdAt: row.createdAt,
|
|
2910
|
+
updatedAt: row.updatedAt
|
|
2911
|
+
}));
|
|
2912
|
+
}
|
|
2913
|
+
deleteByChat(id, chatId) {
|
|
2914
|
+
const now = this.now().toISOString();
|
|
2915
|
+
const result = this.database.prepare(
|
|
2916
|
+
`
|
|
2917
|
+
UPDATE cron_jobs
|
|
2918
|
+
SET status = 'deleted', updated_at = @updatedAt
|
|
2919
|
+
WHERE id = @id AND chat_id = @chatId AND status = 'active'
|
|
2920
|
+
`
|
|
2921
|
+
).run({ id, chatId, updatedAt: now });
|
|
2922
|
+
return result.changes > 0;
|
|
2923
|
+
}
|
|
2924
|
+
markSuccess(id, ranAt) {
|
|
2925
|
+
const job = this.get(id);
|
|
2926
|
+
if (!job) {
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
const nextRunAt = getNextCronRun(job.schedule, ranAt);
|
|
2930
|
+
if (!nextRunAt) {
|
|
2931
|
+
throw new Error("\u65E0\u6CD5\u8BA1\u7B97\u4E0B\u4E00\u6B21\u6267\u884C\u65F6\u95F4\u3002");
|
|
2932
|
+
}
|
|
2933
|
+
this.database.prepare(
|
|
2934
|
+
`
|
|
2935
|
+
UPDATE cron_jobs
|
|
2936
|
+
SET last_run_at = @lastRunAt, next_run_at = @nextRunAt, last_error = NULL, updated_at = @updatedAt
|
|
2937
|
+
WHERE id = @id AND status = 'active'
|
|
2938
|
+
`
|
|
2939
|
+
).run({
|
|
2940
|
+
id,
|
|
2941
|
+
lastRunAt: ranAt.toISOString(),
|
|
2942
|
+
nextRunAt: nextRunAt.toISOString(),
|
|
2943
|
+
updatedAt: ranAt.toISOString()
|
|
2767
2944
|
});
|
|
2768
2945
|
}
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2946
|
+
markFailure(id, error, failedAt) {
|
|
2947
|
+
const job = this.get(id);
|
|
2948
|
+
if (!job) {
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
const nextRunAt = getNextCronRun(job.schedule, failedAt);
|
|
2952
|
+
if (!nextRunAt) {
|
|
2953
|
+
throw new Error("\u65E0\u6CD5\u8BA1\u7B97\u4E0B\u4E00\u6B21\u6267\u884C\u65F6\u95F4\u3002");
|
|
2954
|
+
}
|
|
2955
|
+
this.database.prepare(
|
|
2956
|
+
`
|
|
2957
|
+
UPDATE cron_jobs
|
|
2958
|
+
SET last_run_at = @lastRunAt, last_error = @lastError, next_run_at = @nextRunAt, updated_at = @updatedAt
|
|
2959
|
+
WHERE id = @id AND status = 'active'
|
|
2960
|
+
`
|
|
2961
|
+
).run({
|
|
2962
|
+
id,
|
|
2963
|
+
lastRunAt: failedAt.toISOString(),
|
|
2964
|
+
lastError: error,
|
|
2965
|
+
nextRunAt: nextRunAt.toISOString(),
|
|
2966
|
+
updatedAt: failedAt.toISOString()
|
|
2967
|
+
});
|
|
2782
2968
|
}
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2969
|
+
listByWhere(whereSql, params, limit) {
|
|
2970
|
+
const rows = this.database.prepare(
|
|
2971
|
+
`
|
|
2972
|
+
SELECT
|
|
2973
|
+
id,
|
|
2974
|
+
chat_id AS chatId,
|
|
2975
|
+
created_by_open_id AS createdByOpenId,
|
|
2976
|
+
schedule,
|
|
2977
|
+
prompt,
|
|
2978
|
+
status,
|
|
2979
|
+
last_run_at AS lastRunAt,
|
|
2980
|
+
next_run_at AS nextRunAt,
|
|
2981
|
+
last_error AS lastError,
|
|
2982
|
+
created_at AS createdAt,
|
|
2983
|
+
updated_at AS updatedAt
|
|
2984
|
+
FROM cron_jobs
|
|
2985
|
+
${whereSql}
|
|
2986
|
+
ORDER BY updated_at DESC
|
|
2987
|
+
LIMIT ?
|
|
2988
|
+
`
|
|
2989
|
+
).all(...params, limit);
|
|
2990
|
+
return rows.map((row) => ({
|
|
2991
|
+
id: row.id,
|
|
2992
|
+
chatId: row.chatId,
|
|
2993
|
+
createdByOpenId: row.createdByOpenId ?? void 0,
|
|
2994
|
+
schedule: row.schedule,
|
|
2995
|
+
prompt: row.prompt,
|
|
2996
|
+
status: row.status,
|
|
2997
|
+
lastRunAt: row.lastRunAt ?? void 0,
|
|
2998
|
+
nextRunAt: row.nextRunAt,
|
|
2999
|
+
lastError: row.lastError ?? void 0,
|
|
3000
|
+
createdAt: row.createdAt,
|
|
3001
|
+
updatedAt: row.updatedAt
|
|
3002
|
+
}));
|
|
2803
3003
|
}
|
|
2804
|
-
|
|
2805
|
-
model: input2.config.embedding.model
|
|
2806
|
-
});
|
|
2807
|
-
const embedding = input2.embedding ?? createEmbeddingModel(input2.config, input2.secrets);
|
|
2808
|
-
const stats = await indexMessageChunks({
|
|
2809
|
-
messages: new MessageRepository(input2.database),
|
|
2810
|
-
embedding,
|
|
2811
|
-
store: vectorStore,
|
|
2812
|
-
limit: input2.limit
|
|
2813
|
-
});
|
|
2814
|
-
return {
|
|
2815
|
-
status: "completed",
|
|
2816
|
-
chunks: stats.chunks,
|
|
2817
|
-
vectors: stats.vectors,
|
|
2818
|
-
startedAt,
|
|
2819
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2820
|
-
};
|
|
2821
|
-
}
|
|
3004
|
+
};
|
|
2822
3005
|
|
|
2823
|
-
// src/
|
|
2824
|
-
|
|
2825
|
-
function
|
|
2826
|
-
|
|
3006
|
+
// src/cron/generator.ts
|
|
3007
|
+
var SYSTEM_PROMPT = "\u4F60\u6B63\u5728\u4E3A\u98DE\u4E66\u7FA4\u751F\u6210\u4E00\u6761\u5B9A\u65F6\u6D88\u606F\u3002\u53EF\u4EE5\u5148\u8C03\u7528\u641C\u7D22\u5DE5\u5177\u68C0\u7D22\u672C\u5730\u7FA4\u804A\u77E5\u8BC6\u5E93\u3002\u6700\u7EC8\u8F93\u51FA\u5FC5\u987B\u662F\u53EF\u4EE5\u76F4\u63A5\u53D1\u5230\u7FA4\u91CC\u7684\u7EAF\u6587\u672C\uFF0C\u4E0D\u8981\u8F93\u51FA\u5DE5\u5177\u8C03\u7528\u8BF4\u660E\u3002";
|
|
3008
|
+
function evidenceToText(evidence) {
|
|
3009
|
+
if (evidence.length === 0) {
|
|
3010
|
+
return "\u65E0\u68C0\u7D22\u8BC1\u636E\u3002";
|
|
3011
|
+
}
|
|
3012
|
+
return evidence.map((item, index2) => `${index2 + 1}. ${item.text}`).join("\n");
|
|
2827
3013
|
}
|
|
2828
|
-
function
|
|
2829
|
-
return
|
|
3014
|
+
function toolResultContent(results) {
|
|
3015
|
+
return JSON.stringify(results.map((item) => ({ id: item.id, text: item.text, score: item.score, source: item.source })));
|
|
2830
3016
|
}
|
|
2831
|
-
function
|
|
2832
|
-
if (!
|
|
2833
|
-
|
|
3017
|
+
async function generateCronJobMessage(input2) {
|
|
3018
|
+
if (!input2.model.completeWithTools) {
|
|
3019
|
+
throw new Error("\u5F53\u524D LLM \u5BA2\u6237\u7AEF\u4E0D\u652F\u6301\u5DE5\u5177\u8C03\u7528\u3002");
|
|
2834
3020
|
}
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
3021
|
+
const messages = [
|
|
3022
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
3023
|
+
{ role: "user", content: `\u5F53\u524D\u65F6\u95F4\uFF1A${input2.now.toISOString()}
|
|
3024
|
+
\u4EFB\u52A1\u63D0\u793A\u8BCD\uFF1A${input2.prompt}` }
|
|
3025
|
+
];
|
|
3026
|
+
const toolsByName = new Map(input2.tools.map((tool) => [tool.name, tool]));
|
|
3027
|
+
const evidence = [];
|
|
3028
|
+
const maxModelTurns = input2.maxModelTurns ?? 3;
|
|
3029
|
+
const maxToolCalls = input2.maxToolCalls ?? 6;
|
|
3030
|
+
let toolCallsUsed = 0;
|
|
3031
|
+
for (let turn = 0; turn < maxModelTurns; turn += 1) {
|
|
3032
|
+
const result = await input2.model.completeWithTools(messages, input2.tools);
|
|
3033
|
+
messages.push({ role: "assistant", content: result.content, toolCalls: result.toolCalls, reasoningContent: result.reasoningContent });
|
|
3034
|
+
if (result.toolCalls.length === 0) {
|
|
3035
|
+
break;
|
|
3036
|
+
}
|
|
3037
|
+
for (const call of result.toolCalls) {
|
|
3038
|
+
if (toolCallsUsed >= maxToolCalls) {
|
|
3039
|
+
return input2.model.complete([
|
|
3040
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
3041
|
+
{
|
|
3042
|
+
role: "user",
|
|
3043
|
+
content: `\u5F53\u524D\u65F6\u95F4\uFF1A${input2.now.toISOString()}
|
|
3044
|
+
\u4EFB\u52A1\u63D0\u793A\u8BCD\uFF1A${input2.prompt}
|
|
3045
|
+
|
|
3046
|
+
\u8BC1\u636E\uFF1A
|
|
3047
|
+
${evidenceToText(evidence)}`
|
|
3048
|
+
}
|
|
3049
|
+
]);
|
|
3050
|
+
}
|
|
3051
|
+
toolCallsUsed += 1;
|
|
3052
|
+
const tool = toolsByName.get(call.name);
|
|
3053
|
+
if (!tool) {
|
|
3054
|
+
messages.push({ role: "tool", toolCallId: call.id, content: JSON.stringify({ error: `\u672A\u77E5\u5DE5\u5177\uFF1A${call.name}` }) });
|
|
3055
|
+
continue;
|
|
3056
|
+
}
|
|
3057
|
+
try {
|
|
3058
|
+
const results = await tool.execute(call.input);
|
|
3059
|
+
evidence.push(...results);
|
|
3060
|
+
messages.push({ role: "tool", toolCallId: call.id, content: toolResultContent(results) });
|
|
3061
|
+
} catch (error) {
|
|
3062
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3063
|
+
messages.push({ role: "tool", toolCallId: call.id, content: JSON.stringify({ error: message }) });
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
return input2.model.complete([
|
|
3068
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
3069
|
+
{
|
|
3070
|
+
role: "user",
|
|
3071
|
+
content: `\u5F53\u524D\u65F6\u95F4\uFF1A${input2.now.toISOString()}
|
|
3072
|
+
\u4EFB\u52A1\u63D0\u793A\u8BCD\uFF1A${input2.prompt}
|
|
3073
|
+
|
|
3074
|
+
\u8BC1\u636E\uFF1A
|
|
3075
|
+
${evidenceToText(evidence)}`
|
|
3076
|
+
}
|
|
3077
|
+
]);
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
// src/cron/scheduler.ts
|
|
3081
|
+
function createCronJobScheduler(options) {
|
|
3082
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3083
|
+
const setIntervalFn = options.setIntervalFn ?? setInterval;
|
|
3084
|
+
const clearIntervalFn = options.clearIntervalFn ?? clearInterval;
|
|
3085
|
+
const logger = options.logger ?? console;
|
|
3086
|
+
let timer;
|
|
3087
|
+
let running = false;
|
|
3088
|
+
const runDueNow = async () => {
|
|
3089
|
+
if (running) {
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
running = true;
|
|
3093
|
+
const startedAt = now();
|
|
3094
|
+
try {
|
|
3095
|
+
const jobs = options.repository.listDue(startedAt);
|
|
3096
|
+
for (const job of jobs) {
|
|
3097
|
+
try {
|
|
3098
|
+
const text = await options.generateMessage(job, startedAt);
|
|
3099
|
+
await options.sendTextToChat(job.chatId, text);
|
|
3100
|
+
options.repository.markSuccess(job.id, startedAt);
|
|
3101
|
+
} catch (error) {
|
|
3102
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3103
|
+
options.repository.markFailure(job.id, message, startedAt);
|
|
3104
|
+
logger.error(`CRONJob \u6267\u884C\u5931\u8D25\uFF1A${job.id} ${message}`);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
} finally {
|
|
3108
|
+
running = false;
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
return {
|
|
3112
|
+
start() {
|
|
3113
|
+
if (timer) {
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
void runDueNow();
|
|
3117
|
+
timer = setIntervalFn(() => {
|
|
3118
|
+
void runDueNow();
|
|
3119
|
+
}, 6e4);
|
|
3120
|
+
},
|
|
3121
|
+
stop() {
|
|
3122
|
+
if (!timer) {
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
clearIntervalFn(timer);
|
|
3126
|
+
timer = void 0;
|
|
3127
|
+
},
|
|
3128
|
+
runDueNow
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
// src/gateway/indexing-scheduler.ts
|
|
3133
|
+
function createIndexingScheduler(options) {
|
|
3134
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3135
|
+
const setIntervalFn = options.setIntervalFn ?? setInterval;
|
|
3136
|
+
const clearIntervalFn = options.clearIntervalFn ?? clearInterval;
|
|
3137
|
+
const logger = options.logger ?? console;
|
|
3138
|
+
const parsed = parseCronSchedule2(options.schedule);
|
|
3139
|
+
let timer;
|
|
3140
|
+
let running = false;
|
|
3141
|
+
const runDueNow = async () => {
|
|
3142
|
+
if (!parsed || running || !matchesParsedSchedule2(parsed, now())) {
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
running = true;
|
|
3146
|
+
try {
|
|
3147
|
+
await options.work();
|
|
3148
|
+
} catch (error) {
|
|
3149
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3150
|
+
logger.error(`\u5B9A\u65F6\u6D88\u606F\u7D22\u5F15\u5931\u8D25\uFF1A${message}`);
|
|
3151
|
+
} finally {
|
|
3152
|
+
running = false;
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
return {
|
|
3156
|
+
start() {
|
|
3157
|
+
if (!parsed || timer) {
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3160
|
+
timer = setIntervalFn(() => {
|
|
3161
|
+
void runDueNow();
|
|
3162
|
+
}, 6e4);
|
|
3163
|
+
},
|
|
3164
|
+
stop() {
|
|
3165
|
+
if (!timer) {
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
clearIntervalFn(timer);
|
|
3169
|
+
timer = void 0;
|
|
3170
|
+
},
|
|
3171
|
+
runDueNow
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3174
|
+
function matchesParsedSchedule2(schedule, date) {
|
|
3175
|
+
return schedule.minute(date.getMinutes()) && schedule.hour(date.getHours()) && schedule.dayOfMonth(date.getDate()) && schedule.month(date.getMonth() + 1) && schedule.dayOfWeek(date.getDay());
|
|
3176
|
+
}
|
|
3177
|
+
function parseCronSchedule2(schedule) {
|
|
3178
|
+
const fields = schedule.trim().split(/\s+/);
|
|
3179
|
+
if (fields.length !== 5) {
|
|
3180
|
+
return null;
|
|
3181
|
+
}
|
|
3182
|
+
const minute = parseMinuteField2(fields[0]);
|
|
3183
|
+
const hour = parseExactOrWildcardField2(fields[1], 0, 23);
|
|
3184
|
+
const dayOfMonth = parseExactOrWildcardField2(fields[2], 1, 31);
|
|
3185
|
+
const month = parseExactOrWildcardField2(fields[3], 1, 12);
|
|
3186
|
+
const dayOfWeek = parseExactOrWildcardField2(fields[4], 0, 6);
|
|
3187
|
+
if (!minute || !hour || !dayOfMonth || !month || !dayOfWeek) {
|
|
3188
|
+
return null;
|
|
3189
|
+
}
|
|
3190
|
+
return { minute, hour, dayOfMonth, month, dayOfWeek };
|
|
3191
|
+
}
|
|
3192
|
+
function parseMinuteField2(field) {
|
|
3193
|
+
if (field === "*") {
|
|
3194
|
+
return () => true;
|
|
3195
|
+
}
|
|
3196
|
+
const stepMatch = /^\*\/(\d+)$/.exec(field);
|
|
3197
|
+
if (stepMatch) {
|
|
3198
|
+
const step = Number(stepMatch[1]);
|
|
3199
|
+
if (!Number.isInteger(step) || step <= 0 || step > 59) {
|
|
3200
|
+
return null;
|
|
3201
|
+
}
|
|
3202
|
+
return (value) => value % step === 0;
|
|
3203
|
+
}
|
|
3204
|
+
if (field.includes(",")) {
|
|
3205
|
+
const values = field.split(",").map((part) => parseExactNumber2(part, 0, 59));
|
|
3206
|
+
if (values.some((value) => value === null)) {
|
|
3207
|
+
return null;
|
|
3208
|
+
}
|
|
3209
|
+
const allowed = new Set(values);
|
|
3210
|
+
return (value) => allowed.has(value);
|
|
3211
|
+
}
|
|
3212
|
+
const exact = parseExactNumber2(field, 0, 59);
|
|
3213
|
+
if (exact === null) {
|
|
3214
|
+
return null;
|
|
3215
|
+
}
|
|
3216
|
+
return (value) => value === exact;
|
|
3217
|
+
}
|
|
3218
|
+
function parseExactOrWildcardField2(field, min, max) {
|
|
3219
|
+
if (field === "*") {
|
|
3220
|
+
return () => true;
|
|
3221
|
+
}
|
|
3222
|
+
const exact = parseExactNumber2(field, min, max);
|
|
3223
|
+
if (exact === null) {
|
|
3224
|
+
return null;
|
|
3225
|
+
}
|
|
3226
|
+
return (value) => value === exact;
|
|
3227
|
+
}
|
|
3228
|
+
function parseExactNumber2(field, min, max) {
|
|
3229
|
+
if (!/^\d+$/.test(field)) {
|
|
3230
|
+
return null;
|
|
3231
|
+
}
|
|
3232
|
+
const value = Number(field);
|
|
3233
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
3234
|
+
return null;
|
|
3235
|
+
}
|
|
3236
|
+
return value;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
// src/rag/indexer.ts
|
|
3240
|
+
async function indexMessageChunks(input2) {
|
|
3241
|
+
const chunks = input2.messageIds ? input2.messages.listMessageChunksByMessageIds(input2.messageIds, input2.limit ?? 1e4) : input2.messages.listAllMessageChunks(input2.limit ?? 1e4);
|
|
3242
|
+
if (chunks.length === 0) {
|
|
3243
|
+
return { chunks: 0, vectors: 0 };
|
|
3244
|
+
}
|
|
3245
|
+
const vectors = await input2.embedding.embedBatch(chunks.map((chunk) => chunk.text));
|
|
3246
|
+
const records = [];
|
|
3247
|
+
for (const [index2, chunk] of chunks.entries()) {
|
|
3248
|
+
const vector = vectors[index2];
|
|
3249
|
+
if (!vector || vector.length === 0) {
|
|
3250
|
+
continue;
|
|
3251
|
+
}
|
|
3252
|
+
records.push({
|
|
3253
|
+
id: chunk.chunkId,
|
|
3254
|
+
vector,
|
|
3255
|
+
evidence: {
|
|
3256
|
+
id: chunk.chunkId,
|
|
3257
|
+
text: chunk.text,
|
|
3258
|
+
score: 1,
|
|
3259
|
+
source: toEvidenceSource3(chunk)
|
|
3260
|
+
}
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
await input2.store.upsert(records);
|
|
3264
|
+
return {
|
|
3265
|
+
chunks: chunks.length,
|
|
3266
|
+
vectors: records.length
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
function toEvidenceSource3(chunk) {
|
|
3270
|
+
if (chunk.messageType === "file") {
|
|
3271
|
+
return {
|
|
3272
|
+
type: "file",
|
|
3273
|
+
label: chunk.senderName,
|
|
3274
|
+
timestamp: chunk.sentAt
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
return {
|
|
3278
|
+
type: "message",
|
|
3279
|
+
label: chunk.chatName,
|
|
3280
|
+
sender: chunk.senderName,
|
|
3281
|
+
timestamp: chunk.sentAt
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
// src/rag/manual-index.ts
|
|
3286
|
+
async function processMessagesNow(input2) {
|
|
3287
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3288
|
+
if (!hasEmbeddingConfig(input2.config, input2.secrets)) {
|
|
3289
|
+
return {
|
|
3290
|
+
status: "skipped",
|
|
3291
|
+
reason: "Embedding \u914D\u7F6E\u4E0D\u5B8C\u6574\uFF1BSQLite FTS \u5DF2\u5728\u6D88\u606F\u5165\u5E93\u65F6\u5373\u65F6\u66F4\u65B0\u3002",
|
|
3292
|
+
chunks: 0,
|
|
3293
|
+
vectors: 0,
|
|
3294
|
+
startedAt,
|
|
3295
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3296
|
+
};
|
|
3297
|
+
}
|
|
3298
|
+
const vectorStore = new SqliteVectorStore(input2.database, {
|
|
3299
|
+
model: input2.config.embedding.model
|
|
3300
|
+
});
|
|
3301
|
+
const embedding = input2.embedding ?? createEmbeddingModel(input2.config, input2.secrets);
|
|
3302
|
+
const stats = await indexMessageChunks({
|
|
3303
|
+
messages: new MessageRepository(input2.database),
|
|
3304
|
+
embedding,
|
|
3305
|
+
store: vectorStore,
|
|
3306
|
+
limit: input2.limit
|
|
3307
|
+
});
|
|
3308
|
+
return {
|
|
3309
|
+
status: "completed",
|
|
3310
|
+
chunks: stats.chunks,
|
|
3311
|
+
vectors: stats.vectors,
|
|
3312
|
+
startedAt,
|
|
3313
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
// src/multimodal/tasks.ts
|
|
3318
|
+
import crypto5 from "crypto";
|
|
3319
|
+
function nowIso4() {
|
|
3320
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3321
|
+
}
|
|
3322
|
+
function stableId3(sourceMessageId, imageKey) {
|
|
3323
|
+
return crypto5.createHash("sha256").update(`${sourceMessageId}${imageKey}`).digest("hex").slice(0, 32);
|
|
3324
|
+
}
|
|
3325
|
+
function mapRow(row) {
|
|
3326
|
+
if (!row) {
|
|
3327
|
+
return void 0;
|
|
3328
|
+
}
|
|
3329
|
+
return {
|
|
3330
|
+
id: row.id,
|
|
3331
|
+
sourceMessageId: row.source_message_id,
|
|
3332
|
+
platformMessageId: row.platform_message_id,
|
|
3333
|
+
imageKey: row.image_key,
|
|
3334
|
+
storedPath: row.stored_path,
|
|
3335
|
+
mimeType: row.mime_type,
|
|
3336
|
+
status: row.status,
|
|
3337
|
+
attempts: row.attempts,
|
|
3338
|
+
...row.last_error ? { lastError: row.last_error } : {},
|
|
3339
|
+
...row.derived_message_id ? { derivedMessageId: row.derived_message_id } : {},
|
|
3340
|
+
createdAt: row.created_at,
|
|
2847
3341
|
updatedAt: row.updated_at
|
|
2848
3342
|
};
|
|
2849
3343
|
}
|
|
@@ -3082,231 +3576,79 @@ var ImageMultimodalWorker = class {
|
|
|
3082
3576
|
}
|
|
3083
3577
|
};
|
|
3084
3578
|
|
|
3085
|
-
// src/
|
|
3086
|
-
function
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
if (!value) {
|
|
3091
|
-
return "\u672A\u77E5\u65F6\u95F4";
|
|
3092
|
-
}
|
|
3093
|
-
const date = new Date(value);
|
|
3094
|
-
if (Number.isNaN(date.getTime())) {
|
|
3095
|
-
return value;
|
|
3096
|
-
}
|
|
3097
|
-
const pad = (input2) => String(input2).padStart(2, "0");
|
|
3098
|
-
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
3099
|
-
}
|
|
3100
|
-
function formatSpeaker(source) {
|
|
3101
|
-
if (source.type === "file") {
|
|
3102
|
-
return isOpaqueId(source.label) ? "\u6587\u4EF6" : `\u6587\u4EF6 ${source.label}`;
|
|
3103
|
-
}
|
|
3104
|
-
if (source.sender && !isOpaqueId(source.sender)) {
|
|
3105
|
-
return source.sender;
|
|
3106
|
-
}
|
|
3107
|
-
return "\u7FA4\u6210\u5458";
|
|
3108
|
-
}
|
|
3109
|
-
function clipText(value, maxLength) {
|
|
3110
|
-
const normalized = value.replace(/\s+/g, " ").trim();
|
|
3111
|
-
return normalized.length > maxLength ? `${normalized.slice(0, maxLength)}...` : normalized;
|
|
3112
|
-
}
|
|
3113
|
-
function formatCitation(citation, options = {}) {
|
|
3114
|
-
const maxTextLength = options.maxTextLength ?? 120;
|
|
3115
|
-
const speaker = formatSpeaker(citation.source);
|
|
3116
|
-
const time = formatTime(citation.source.timestamp);
|
|
3117
|
-
const verb = citation.source.type === "file" ? "\u8BB0\u5F55" : "\u8BF4";
|
|
3118
|
-
return `[${citation.marker}] ${speaker}\u5728 ${time} ${verb}\uFF1A\u201C${clipText(citation.text, maxTextLength)}\u201D`;
|
|
3119
|
-
}
|
|
3120
|
-
function formatCitations(citations, options = {}) {
|
|
3121
|
-
return citations.map((citation) => formatCitation(citation, options)).join("\n");
|
|
3122
|
-
}
|
|
3123
|
-
|
|
3124
|
-
// src/rag/answer.ts
|
|
3125
|
-
var DEFAULT_MAX_EVIDENCE_BLOCKS = 8;
|
|
3126
|
-
var DEFAULT_MAX_CHARS_PER_BLOCK = 1200;
|
|
3127
|
-
var SCORE_TIE_THRESHOLD = 0.15;
|
|
3128
|
-
function parseTimestamp(value) {
|
|
3129
|
-
if (!value) {
|
|
3130
|
-
return 0;
|
|
3579
|
+
// src/cron/tools.ts
|
|
3580
|
+
function readString(input2, key) {
|
|
3581
|
+
const value = typeof input2 === "object" && input2 !== null && key in input2 ? input2[key] : void 0;
|
|
3582
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
3583
|
+
throw new Error(`${key} \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u3002`);
|
|
3131
3584
|
}
|
|
3132
|
-
|
|
3133
|
-
return Number.isFinite(time) ? time : 0;
|
|
3585
|
+
return value.trim();
|
|
3134
3586
|
}
|
|
3135
|
-
function
|
|
3136
|
-
return [
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
const selected = rankEvidenceForPrompt(evidence).slice(0, maxEvidenceBlocks);
|
|
3155
|
-
const citations = selected.map((item, index2) => ({
|
|
3156
|
-
marker: `S${index2 + 1}`,
|
|
3157
|
-
evidenceId: item.id,
|
|
3158
|
-
source: item.source,
|
|
3159
|
-
text: item.text
|
|
3160
|
-
}));
|
|
3161
|
-
const evidenceText = selected.map((item, index2) => {
|
|
3162
|
-
const marker = citations[index2]?.marker;
|
|
3163
|
-
const clippedText = item.text.length > maxCharsPerBlock ? `${item.text.slice(0, maxCharsPerBlock)}...` : item.text;
|
|
3164
|
-
const sourceParts = [
|
|
3165
|
-
item.source.label,
|
|
3166
|
-
item.source.sender ? `\u53D1\u9001\u4EBA\uFF1A${item.source.sender}` : void 0,
|
|
3167
|
-
item.source.timestamp ? `\u65F6\u95F4\uFF1A${item.source.timestamp}` : void 0,
|
|
3168
|
-
item.source.location ? `\u4F4D\u7F6E\uFF1A${item.source.location}` : void 0
|
|
3169
|
-
].filter(Boolean);
|
|
3170
|
-
return `[${marker}]
|
|
3171
|
-
\u6765\u6E90\uFF1A${sourceParts.join("\uFF1B")}
|
|
3172
|
-
\u5185\u5BB9\uFF1A${clippedText}`;
|
|
3173
|
-
}).join("\n\n");
|
|
3174
|
-
return {
|
|
3175
|
-
citations,
|
|
3176
|
-
messages: [
|
|
3177
|
-
{
|
|
3178
|
-
role: "system",
|
|
3179
|
-
content: "\u4F60\u662F ChatterCatcher \u7684\u95EE\u7B54\u6A21\u5757\u3002\u53EA\u80FD\u6839\u636E\u63D0\u4F9B\u7684\u68C0\u7D22\u8BC1\u636E\u56DE\u7B54\uFF0C\u5FC5\u987B\u7B80\u77ED\u76F4\u63A5\u3002\u4E8B\u5B9E\u6027\u7ED3\u8BBA\u5FC5\u987B\u5F15\u7528 [S1] \u8FD9\u6837\u7684\u6765\u6E90\u6807\u8BB0\u3002\u8BC1\u636E\u4E0D\u8DB3\u65F6\u8BF4\u4E0D\u77E5\u9053\uFF0C\u4E0D\u8981\u731C\u3002\u82E5\u8BC1\u636E\u4E92\u76F8\u77DB\u76FE\uFF0C\u4F18\u5148\u91C7\u7528\u65F6\u95F4\u66F4\u65B0\u4E14\u8868\u8FF0\u660E\u786E\u7684\u8BC1\u636E\uFF1B\u5982\u679C\u8F83\u65B0\u7684\u8BC1\u636E\u53EA\u662F\u8BA8\u8BBA\u3001\u731C\u6D4B\u6216\u4E0D\u786E\u5B9A\u8868\u8FBE\uFF0C\u4E0D\u8981\u628A\u5B83\u5F53\u4F5C\u786E\u5B9A\u66F4\u65B0\u3002"
|
|
3587
|
+
function createCronJobTools(input2) {
|
|
3588
|
+
return [
|
|
3589
|
+
{
|
|
3590
|
+
name: "create_cron_job",
|
|
3591
|
+
description: "Create a scheduled AI message for the current Feishu chat only. The schedule must be a five-field cron string.",
|
|
3592
|
+
inputSchema: {
|
|
3593
|
+
type: "object",
|
|
3594
|
+
properties: {
|
|
3595
|
+
schedule: {
|
|
3596
|
+
type: "string",
|
|
3597
|
+
description: "Five-field cron schedule, for example 0 9 * * *."
|
|
3598
|
+
},
|
|
3599
|
+
prompt: {
|
|
3600
|
+
type: "string",
|
|
3601
|
+
description: "Prompt used later to generate the scheduled message."
|
|
3602
|
+
}
|
|
3603
|
+
},
|
|
3604
|
+
required: ["schedule", "prompt"],
|
|
3605
|
+
additionalProperties: false
|
|
3180
3606
|
},
|
|
3181
|
-
{
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
2. \u540C\u4E00\u4E8B\u9879\u51FA\u73B0\u591A\u4E2A\u7248\u672C\u65F6\uFF0C\u9ED8\u8BA4\u8F83\u65B0\u7684\u660E\u786E\u6D88\u606F\u4F18\u5148\u3002
|
|
3188
|
-
3. \u56DE\u7B54\u53EA\u5F15\u7528\u5B9E\u9645\u652F\u6491\u7ED3\u8BBA\u7684\u8BC1\u636E\u3002
|
|
3189
|
-
|
|
3190
|
-
\u68C0\u7D22\u8BC1\u636E\uFF1A
|
|
3191
|
-
${evidenceText}`
|
|
3192
|
-
}
|
|
3193
|
-
]
|
|
3194
|
-
};
|
|
3195
|
-
}
|
|
3196
|
-
async function generateGroundedAnswer(input2) {
|
|
3197
|
-
const prompt = buildEvidencePrompt(input2.question, input2.evidence);
|
|
3198
|
-
const answer = await input2.model.complete(prompt.messages);
|
|
3199
|
-
return {
|
|
3200
|
-
answer,
|
|
3201
|
-
citations: prompt.citations
|
|
3202
|
-
};
|
|
3203
|
-
}
|
|
3204
|
-
|
|
3205
|
-
// src/rag/agentic-qa-service.ts
|
|
3206
|
-
var DEFAULT_MAX_MODEL_TURNS = 4;
|
|
3207
|
-
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
3208
|
-
var DEFAULT_MAX_EVIDENCE = 12;
|
|
3209
|
-
var NO_EVIDENCE_ANSWER = "\u4E0D\u77E5\u9053\u3002\u5F53\u524D\u672C\u5730\u77E5\u8BC6\u5E93\u6CA1\u6709\u68C0\u7D22\u5230\u8DB3\u591F\u8BC1\u636E\u3002";
|
|
3210
|
-
var AGENTIC_SYSTEM_PROMPT = "\u4F60\u662F\u672C\u5730\u77E5\u8BC6\u4FE1\u606F\u6536\u96C6\u4EE3\u7406\u3002\u4F60\u7684\u804C\u8D23\u662F\u56F4\u7ED5\u7528\u6237\u95EE\u9898\u51B3\u5B9A\u662F\u5426\u8C03\u7528\u641C\u7D22\u5DE5\u5177\u3001\u9009\u62E9\u5408\u9002\u7684\u5DE5\u5177\u548C\u67E5\u8BE2\u8BCD\uFF0C\u5E76\u6839\u636E\u5F53\u524D\u7ED3\u679C\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u641C\u7D22\u3002\u4E0D\u8981\u7F16\u9020\u4EFB\u4F55\u8BC1\u636E\u6216\u58F0\u79F0\u770B\u8FC7\u672A\u68C0\u7D22\u5230\u7684\u5185\u5BB9\u3002\u4F60\u7684\u8F93\u51FA\u53EA\u7528\u4E8E\u6536\u96C6\u8BC1\u636E\uFF0C\u6700\u7EC8\u7B54\u6848\u4F1A\u7531\u53E6\u4E00\u4E2A\u57FA\u4E8E\u8BC1\u636E\u7684\u6B65\u9AA4\u751F\u6210\u3002";
|
|
3211
|
-
function toToolResultContent(results) {
|
|
3212
|
-
return JSON.stringify(
|
|
3213
|
-
results.map((item) => ({
|
|
3214
|
-
id: item.id,
|
|
3215
|
-
text: item.text,
|
|
3216
|
-
score: item.score,
|
|
3217
|
-
source: item.source
|
|
3218
|
-
}))
|
|
3219
|
-
);
|
|
3220
|
-
}
|
|
3221
|
-
function toToolErrorContent(message) {
|
|
3222
|
-
return JSON.stringify({ error: message });
|
|
3223
|
-
}
|
|
3224
|
-
function dedupeEvidence(evidence, maxEvidence) {
|
|
3225
|
-
const deduped = [];
|
|
3226
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3227
|
-
for (const item of evidence) {
|
|
3228
|
-
if (seen.has(item.id)) {
|
|
3229
|
-
continue;
|
|
3230
|
-
}
|
|
3231
|
-
seen.add(item.id);
|
|
3232
|
-
deduped.push(item);
|
|
3233
|
-
if (deduped.length >= maxEvidence) {
|
|
3234
|
-
break;
|
|
3235
|
-
}
|
|
3236
|
-
}
|
|
3237
|
-
return deduped;
|
|
3238
|
-
}
|
|
3239
|
-
async function askWithAgenticRag(input2) {
|
|
3240
|
-
if (!input2.model.completeWithTools) {
|
|
3241
|
-
throw new Error("\u5F53\u524D LLM \u5BA2\u6237\u7AEF\u4E0D\u652F\u6301\u5DE5\u5177\u8C03\u7528\u3002");
|
|
3242
|
-
}
|
|
3243
|
-
const maxModelTurns = input2.maxModelTurns ?? DEFAULT_MAX_MODEL_TURNS;
|
|
3244
|
-
const maxToolCalls = input2.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
3245
|
-
const maxEvidence = input2.maxEvidence ?? DEFAULT_MAX_EVIDENCE;
|
|
3246
|
-
const messages = [
|
|
3247
|
-
{ role: "system", content: AGENTIC_SYSTEM_PROMPT },
|
|
3248
|
-
{ role: "user", content: input2.question }
|
|
3249
|
-
];
|
|
3250
|
-
const toolsByName = new Map(input2.tools.map((tool) => [tool.name, tool]));
|
|
3251
|
-
let evidence = [];
|
|
3252
|
-
let toolCallsUsed = 0;
|
|
3253
|
-
for (let turn = 0; turn < maxModelTurns; turn += 1) {
|
|
3254
|
-
const assistantResult = await input2.model.completeWithTools(messages, input2.tools);
|
|
3255
|
-
messages.push({
|
|
3256
|
-
role: "assistant",
|
|
3257
|
-
content: assistantResult.content,
|
|
3258
|
-
toolCalls: assistantResult.toolCalls
|
|
3259
|
-
});
|
|
3260
|
-
if (assistantResult.toolCalls.length === 0) {
|
|
3261
|
-
break;
|
|
3262
|
-
}
|
|
3263
|
-
for (const toolCall of assistantResult.toolCalls) {
|
|
3264
|
-
if (toolCallsUsed >= maxToolCalls) {
|
|
3265
|
-
break;
|
|
3266
|
-
}
|
|
3267
|
-
toolCallsUsed += 1;
|
|
3268
|
-
const tool = toolsByName.get(toolCall.name);
|
|
3269
|
-
if (!tool) {
|
|
3270
|
-
messages.push({
|
|
3271
|
-
role: "tool",
|
|
3272
|
-
toolCallId: toolCall.id,
|
|
3273
|
-
content: toToolErrorContent(`\u672A\u77E5\u5DE5\u5177\uFF1A${toolCall.name}`)
|
|
3607
|
+
execute: async (rawInput) => {
|
|
3608
|
+
const job = input2.repository.create({
|
|
3609
|
+
chatId: input2.chatId,
|
|
3610
|
+
createdByOpenId: input2.createdByOpenId,
|
|
3611
|
+
schedule: readString(rawInput, "schedule"),
|
|
3612
|
+
prompt: readString(rawInput, "prompt")
|
|
3274
3613
|
});
|
|
3275
|
-
|
|
3614
|
+
return JSON.stringify({ ok: true, job });
|
|
3276
3615
|
}
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3616
|
+
},
|
|
3617
|
+
{
|
|
3618
|
+
name: "list_cron_jobs",
|
|
3619
|
+
description: "List active scheduled AI messages for the current Feishu chat only.",
|
|
3620
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
3621
|
+
execute: async () => JSON.stringify({ ok: true, jobs: input2.repository.listByChat(input2.chatId) })
|
|
3622
|
+
},
|
|
3623
|
+
{
|
|
3624
|
+
name: "delete_cron_job",
|
|
3625
|
+
description: "Delete a scheduled AI message by ID, only if it belongs to the current Feishu chat.",
|
|
3626
|
+
inputSchema: {
|
|
3627
|
+
type: "object",
|
|
3628
|
+
properties: {
|
|
3629
|
+
id: {
|
|
3630
|
+
type: "string",
|
|
3631
|
+
description: "Cron job ID returned by create_cron_job or list_cron_jobs."
|
|
3632
|
+
}
|
|
3633
|
+
},
|
|
3634
|
+
required: ["id"],
|
|
3635
|
+
additionalProperties: false
|
|
3636
|
+
},
|
|
3637
|
+
execute: async (rawInput) => {
|
|
3638
|
+
const id = readString(rawInput, "id");
|
|
3639
|
+
const ok = input2.repository.deleteByChat(id, input2.chatId);
|
|
3640
|
+
return JSON.stringify({
|
|
3641
|
+
ok,
|
|
3642
|
+
id,
|
|
3643
|
+
message: ok ? "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u5220\u9664\u3002" : "\u6CA1\u6709\u627E\u5230\u5F53\u524D\u7FA4\u91CC\u7684\u8FD9\u4E2A\u5B9A\u65F6\u4EFB\u52A1\u3002"
|
|
3291
3644
|
});
|
|
3292
3645
|
}
|
|
3293
3646
|
}
|
|
3294
|
-
|
|
3295
|
-
if (evidence.length === 0) {
|
|
3296
|
-
return {
|
|
3297
|
-
answer: NO_EVIDENCE_ANSWER,
|
|
3298
|
-
citations: []
|
|
3299
|
-
};
|
|
3300
|
-
}
|
|
3301
|
-
return generateGroundedAnswer({
|
|
3302
|
-
question: input2.question,
|
|
3303
|
-
evidence,
|
|
3304
|
-
model: input2.model
|
|
3305
|
-
});
|
|
3647
|
+
];
|
|
3306
3648
|
}
|
|
3307
3649
|
|
|
3308
3650
|
// src/rag/qa-logs.ts
|
|
3309
|
-
import
|
|
3651
|
+
import crypto6 from "crypto";
|
|
3310
3652
|
function clampLimit(limit) {
|
|
3311
3653
|
return Math.max(1, Math.min(200, Math.trunc(limit)));
|
|
3312
3654
|
}
|
|
@@ -3317,7 +3659,7 @@ var QaLogRepository = class {
|
|
|
3317
3659
|
database;
|
|
3318
3660
|
create(input2) {
|
|
3319
3661
|
const record = {
|
|
3320
|
-
id: `qa_${
|
|
3662
|
+
id: `qa_${crypto6.randomUUID()}`,
|
|
3321
3663
|
chatId: input2.chatId ?? null,
|
|
3322
3664
|
questionMessageId: input2.questionMessageId ?? null,
|
|
3323
3665
|
question: input2.question,
|
|
@@ -3430,6 +3772,77 @@ function stripMentions(text, mentions) {
|
|
|
3430
3772
|
}
|
|
3431
3773
|
return result.replace(/@/g, " ").replace(/\s+/g, " ").trim();
|
|
3432
3774
|
}
|
|
3775
|
+
var FEISHU_TOOL_SYSTEM_PROMPT = "\u4F60\u662F\u98DE\u4E66\u7FA4\u804A\u52A9\u624B\u3002\u4F60\u53EF\u4EE5\u5148\u641C\u7D22\u672C\u5730\u77E5\u8BC6\u6765\u56DE\u7B54\u95EE\u9898\uFF1B\u5F53\u7528\u6237\u660E\u786E\u8981\u6C42\u521B\u5EFA\u3001\u67E5\u770B\u6216\u5220\u9664\u7FA4\u6D88\u606F\u5B9A\u65F6\u4EFB\u52A1\u65F6\uFF0C\u4E5F\u53EF\u4EE5\u8C03\u7528\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u3002\u5B9A\u65F6\u4EFB\u52A1\u5DE5\u5177\u53EA\u7BA1\u7406\u5F53\u524D\u7FA4\u804A\uFF0C\u4E0D\u80FD\u8DE8\u7FA4\u64CD\u4F5C\u3002\u82E5\u7528\u6237\u7528\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u65F6\u95F4\uFF0C\u4F60\u9700\u8981\u5148\u5C06\u5176\u8F6C\u6362\u4E3A\u4E94\u5B57\u6BB5 cron \u8868\u8FBE\u5F0F\uFF08\u5206 \u65F6 \u65E5 \u6708 \u5468\uFF09\uFF0C\u518D\u8C03\u7528\u5DE5\u5177\u3002\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";
|
|
3776
|
+
var DEFAULT_MAX_MODEL_TURNS = 4;
|
|
3777
|
+
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
3778
|
+
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";
|
|
3779
|
+
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";
|
|
3780
|
+
function toToolResultContent(value) {
|
|
3781
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
3782
|
+
}
|
|
3783
|
+
function toToolErrorContent(message) {
|
|
3784
|
+
return JSON.stringify({ ok: false, error: message });
|
|
3785
|
+
}
|
|
3786
|
+
async function executeFeishuTool(tool, input2) {
|
|
3787
|
+
const result = await tool.execute(input2);
|
|
3788
|
+
return toToolResultContent(result);
|
|
3789
|
+
}
|
|
3790
|
+
async function runFeishuToolLoop(input2) {
|
|
3791
|
+
if (!input2.model.completeWithTools) {
|
|
3792
|
+
throw new Error("\u5F53\u524D LLM \u5BA2\u6237\u7AEF\u4E0D\u652F\u6301\u5DE5\u5177\u8C03\u7528\u3002");
|
|
3793
|
+
}
|
|
3794
|
+
const maxModelTurns = input2.maxModelTurns ?? DEFAULT_MAX_MODEL_TURNS;
|
|
3795
|
+
const maxToolCalls = input2.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
3796
|
+
const messages = [
|
|
3797
|
+
{ role: "system", content: FEISHU_TOOL_SYSTEM_PROMPT },
|
|
3798
|
+
{ role: "user", content: input2.question }
|
|
3799
|
+
];
|
|
3800
|
+
const toolsByName = new Map(input2.tools.map((tool) => [tool.name, tool]));
|
|
3801
|
+
let toolCallsUsed = 0;
|
|
3802
|
+
for (let turn = 0; turn < maxModelTurns; turn += 1) {
|
|
3803
|
+
const assistantResult = await input2.model.completeWithTools(messages, input2.tools);
|
|
3804
|
+
messages.push({
|
|
3805
|
+
role: "assistant",
|
|
3806
|
+
content: assistantResult.content,
|
|
3807
|
+
toolCalls: assistantResult.toolCalls,
|
|
3808
|
+
reasoningContent: assistantResult.reasoningContent
|
|
3809
|
+
});
|
|
3810
|
+
if (assistantResult.toolCalls.length === 0) {
|
|
3811
|
+
return assistantResult.content || FEISHU_TOOL_LOOP_FALLBACK;
|
|
3812
|
+
}
|
|
3813
|
+
for (const toolCall of assistantResult.toolCalls) {
|
|
3814
|
+
if (toolCallsUsed >= maxToolCalls) {
|
|
3815
|
+
return FEISHU_TOOL_LOOP_LIMIT_REACHED;
|
|
3816
|
+
}
|
|
3817
|
+
toolCallsUsed += 1;
|
|
3818
|
+
const tool = toolsByName.get(toolCall.name);
|
|
3819
|
+
if (!tool) {
|
|
3820
|
+
messages.push({
|
|
3821
|
+
role: "tool",
|
|
3822
|
+
toolCallId: toolCall.id,
|
|
3823
|
+
content: toToolErrorContent(`\u672A\u77E5\u5DE5\u5177\uFF1A${toolCall.name}`)
|
|
3824
|
+
});
|
|
3825
|
+
continue;
|
|
3826
|
+
}
|
|
3827
|
+
try {
|
|
3828
|
+
const result = await executeFeishuTool(tool, toolCall.input);
|
|
3829
|
+
messages.push({
|
|
3830
|
+
role: "tool",
|
|
3831
|
+
toolCallId: toolCall.id,
|
|
3832
|
+
content: result
|
|
3833
|
+
});
|
|
3834
|
+
} catch (error) {
|
|
3835
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3836
|
+
messages.push({
|
|
3837
|
+
role: "tool",
|
|
3838
|
+
toolCallId: toolCall.id,
|
|
3839
|
+
content: toToolErrorContent(message)
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
return FEISHU_TOOL_LOOP_FALLBACK;
|
|
3845
|
+
}
|
|
3433
3846
|
function isMentionForBot(mention, config) {
|
|
3434
3847
|
if (!config.feishu.botOpenId) {
|
|
3435
3848
|
return false;
|
|
@@ -3524,27 +3937,28 @@ var FeishuQuestionHandler = class {
|
|
|
3524
3937
|
});
|
|
3525
3938
|
try {
|
|
3526
3939
|
try {
|
|
3527
|
-
const
|
|
3940
|
+
const cronTools = createCronJobTools({
|
|
3941
|
+
repository: new CronJobRepository(this.options.database),
|
|
3942
|
+
chatId: decision.chatId,
|
|
3943
|
+
createdByOpenId: payload.event?.sender?.sender_id?.open_id
|
|
3944
|
+
});
|
|
3945
|
+
const allTools = [...tools, ...cronTools];
|
|
3946
|
+
const answer = await runFeishuToolLoop({
|
|
3528
3947
|
question: decision.question,
|
|
3529
|
-
tools,
|
|
3948
|
+
tools: allTools,
|
|
3530
3949
|
model: this.options.model
|
|
3531
3950
|
});
|
|
3532
3951
|
qaLogs.create({
|
|
3533
3952
|
chatId: decision.chatId,
|
|
3534
3953
|
questionMessageId,
|
|
3535
3954
|
question: decision.question,
|
|
3536
|
-
answer
|
|
3537
|
-
citations:
|
|
3538
|
-
retrievalDebug: {
|
|
3955
|
+
answer,
|
|
3956
|
+
citations: [],
|
|
3957
|
+
retrievalDebug: {},
|
|
3539
3958
|
status: "answered",
|
|
3540
3959
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3541
3960
|
});
|
|
3542
|
-
|
|
3543
|
-
const text = citations ? `${result.answer}
|
|
3544
|
-
|
|
3545
|
-
\u5F15\u7528\uFF1A
|
|
3546
|
-
${citations}` : result.answer;
|
|
3547
|
-
await this.sendResponse(decision.chatId, questionMessageId, text);
|
|
3961
|
+
await this.sendResponse(decision.chatId, questionMessageId, answer);
|
|
3548
3962
|
} catch (error) {
|
|
3549
3963
|
const message = error instanceof Error ? error.message : String(error);
|
|
3550
3964
|
qaLogs.create({
|
|
@@ -3790,18 +4204,39 @@ function createFeishuGateway(options) {
|
|
|
3790
4204
|
});
|
|
3791
4205
|
}
|
|
3792
4206
|
}) : void 0);
|
|
4207
|
+
const cronJobScheduler = options.cronJobScheduler ?? (options.cronJobProcessor ? createCronJobScheduler({
|
|
4208
|
+
repository: new CronJobRepository(options.cronJobProcessor.database),
|
|
4209
|
+
sendTextToChat: (chatId, text) => options.cronJobProcessor.sender.sendTextToChat(chatId, text),
|
|
4210
|
+
generateMessage: async (job, now) => {
|
|
4211
|
+
const { tools, close } = await createAgenticRagSearchTools({
|
|
4212
|
+
config: options.config,
|
|
4213
|
+
secrets: options.secrets,
|
|
4214
|
+
database: options.cronJobProcessor.database,
|
|
4215
|
+
messages: new MessageRepository(options.cronJobProcessor.database),
|
|
4216
|
+
scope: { platform: "feishu", platformChatId: job.chatId }
|
|
4217
|
+
});
|
|
4218
|
+
try {
|
|
4219
|
+
return await generateCronJobMessage({ prompt: job.prompt, model: options.cronJobProcessor.model, tools, now });
|
|
4220
|
+
} finally {
|
|
4221
|
+
close();
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
}) : void 0);
|
|
3793
4225
|
return {
|
|
3794
4226
|
async start() {
|
|
3795
4227
|
try {
|
|
3796
4228
|
await wsClient.start({ eventDispatcher });
|
|
3797
4229
|
indexingScheduler?.start();
|
|
4230
|
+
cronJobScheduler?.start();
|
|
3798
4231
|
} catch (error) {
|
|
3799
4232
|
indexingScheduler?.stop();
|
|
4233
|
+
cronJobScheduler?.stop();
|
|
3800
4234
|
throw formatGatewayStartError(error);
|
|
3801
4235
|
}
|
|
3802
4236
|
},
|
|
3803
4237
|
stop() {
|
|
3804
4238
|
indexingScheduler?.stop();
|
|
4239
|
+
cronJobScheduler?.stop();
|
|
3805
4240
|
wsClient.close({ force: true });
|
|
3806
4241
|
}
|
|
3807
4242
|
};
|
|
@@ -3873,7 +4308,7 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
3873
4308
|
};
|
|
3874
4309
|
|
|
3875
4310
|
// src/files/ingest.ts
|
|
3876
|
-
import
|
|
4311
|
+
import crypto7 from "crypto";
|
|
3877
4312
|
import fs11 from "fs/promises";
|
|
3878
4313
|
import path13 from "path";
|
|
3879
4314
|
|
|
@@ -3937,7 +4372,7 @@ function ensureSupportedTextFile(filePath) {
|
|
|
3937
4372
|
}
|
|
3938
4373
|
}
|
|
3939
4374
|
function stableStoredName(sourcePath, fileName) {
|
|
3940
|
-
const digest =
|
|
4375
|
+
const digest = crypto7.createHash("sha256").update(sourcePath).digest("hex").slice(0, 16);
|
|
3941
4376
|
return `${digest}-${fileName}`;
|
|
3942
4377
|
}
|
|
3943
4378
|
async function ingestLocalFile(input2) {
|
|
@@ -4459,6 +4894,87 @@ function createMultimodalModel(config, secrets) {
|
|
|
4459
4894
|
});
|
|
4460
4895
|
}
|
|
4461
4896
|
|
|
4897
|
+
// src/rag/answer.ts
|
|
4898
|
+
var DEFAULT_MAX_EVIDENCE_BLOCKS = 8;
|
|
4899
|
+
var DEFAULT_MAX_CHARS_PER_BLOCK = 1200;
|
|
4900
|
+
var SCORE_TIE_THRESHOLD = 0.15;
|
|
4901
|
+
function parseTimestamp(value) {
|
|
4902
|
+
if (!value) {
|
|
4903
|
+
return 0;
|
|
4904
|
+
}
|
|
4905
|
+
const time = Date.parse(value);
|
|
4906
|
+
return Number.isFinite(time) ? time : 0;
|
|
4907
|
+
}
|
|
4908
|
+
function rankEvidenceForPrompt(evidence) {
|
|
4909
|
+
return [...evidence].sort((left, right) => {
|
|
4910
|
+
const scoreDiff = right.score - left.score;
|
|
4911
|
+
if (Math.abs(scoreDiff) > SCORE_TIE_THRESHOLD) {
|
|
4912
|
+
return scoreDiff;
|
|
4913
|
+
}
|
|
4914
|
+
const timeDiff = parseTimestamp(right.source.timestamp) - parseTimestamp(left.source.timestamp);
|
|
4915
|
+
if (timeDiff !== 0) {
|
|
4916
|
+
return timeDiff;
|
|
4917
|
+
}
|
|
4918
|
+
return scoreDiff;
|
|
4919
|
+
});
|
|
4920
|
+
}
|
|
4921
|
+
function buildEvidencePrompt(question, evidence, options = {}) {
|
|
4922
|
+
if (evidence.length === 0) {
|
|
4923
|
+
throw new Error("RAG evidence is required before answer generation.");
|
|
4924
|
+
}
|
|
4925
|
+
const maxEvidenceBlocks = options.maxEvidenceBlocks ?? DEFAULT_MAX_EVIDENCE_BLOCKS;
|
|
4926
|
+
const maxCharsPerBlock = options.maxCharsPerBlock ?? DEFAULT_MAX_CHARS_PER_BLOCK;
|
|
4927
|
+
const selected = rankEvidenceForPrompt(evidence).slice(0, maxEvidenceBlocks);
|
|
4928
|
+
const citations = selected.map((item, index2) => ({
|
|
4929
|
+
marker: `S${index2 + 1}`,
|
|
4930
|
+
evidenceId: item.id,
|
|
4931
|
+
source: item.source,
|
|
4932
|
+
text: item.text
|
|
4933
|
+
}));
|
|
4934
|
+
const evidenceText = selected.map((item, index2) => {
|
|
4935
|
+
const marker = citations[index2]?.marker;
|
|
4936
|
+
const clippedText = item.text.length > maxCharsPerBlock ? `${item.text.slice(0, maxCharsPerBlock)}...` : item.text;
|
|
4937
|
+
const sourceParts = [
|
|
4938
|
+
item.source.label,
|
|
4939
|
+
item.source.sender ? `\u53D1\u9001\u4EBA\uFF1A${item.source.sender}` : void 0,
|
|
4940
|
+
item.source.timestamp ? `\u65F6\u95F4\uFF1A${item.source.timestamp}` : void 0,
|
|
4941
|
+
item.source.location ? `\u4F4D\u7F6E\uFF1A${item.source.location}` : void 0
|
|
4942
|
+
].filter(Boolean);
|
|
4943
|
+
return `[${marker}]
|
|
4944
|
+
\u6765\u6E90\uFF1A${sourceParts.join("\uFF1B")}
|
|
4945
|
+
\u5185\u5BB9\uFF1A${clippedText}`;
|
|
4946
|
+
}).join("\n\n");
|
|
4947
|
+
return {
|
|
4948
|
+
citations,
|
|
4949
|
+
messages: [
|
|
4950
|
+
{
|
|
4951
|
+
role: "system",
|
|
4952
|
+
content: "\u4F60\u662F ChatterCatcher \u7684\u95EE\u7B54\u6A21\u5757\u3002\u53EA\u80FD\u6839\u636E\u63D0\u4F9B\u7684\u68C0\u7D22\u8BC1\u636E\u56DE\u7B54\uFF0C\u5FC5\u987B\u7B80\u77ED\u76F4\u63A5\u3002\u4E8B\u5B9E\u6027\u7ED3\u8BBA\u5FC5\u987B\u5F15\u7528 [S1] \u8FD9\u6837\u7684\u6765\u6E90\u6807\u8BB0\u3002\u8BC1\u636E\u4E0D\u8DB3\u65F6\u8BF4\u4E0D\u77E5\u9053\uFF0C\u4E0D\u8981\u731C\u3002\u82E5\u8BC1\u636E\u4E92\u76F8\u77DB\u76FE\uFF0C\u4F18\u5148\u91C7\u7528\u65F6\u95F4\u66F4\u65B0\u4E14\u8868\u8FF0\u660E\u786E\u7684\u8BC1\u636E\uFF1B\u5982\u679C\u8F83\u65B0\u7684\u8BC1\u636E\u53EA\u662F\u8BA8\u8BBA\u3001\u731C\u6D4B\u6216\u4E0D\u786E\u5B9A\u8868\u8FBE\uFF0C\u4E0D\u8981\u628A\u5B83\u5F53\u4F5C\u786E\u5B9A\u66F4\u65B0\u3002"
|
|
4953
|
+
},
|
|
4954
|
+
{
|
|
4955
|
+
role: "user",
|
|
4956
|
+
content: `\u95EE\u9898\uFF1A${question}
|
|
4957
|
+
|
|
4958
|
+
\u8BC1\u636E\u5904\u7406\u89C4\u5219\uFF1A
|
|
4959
|
+
1. \u5148\u5224\u65AD\u8BC1\u636E\u662F\u5426\u8DB3\u4EE5\u56DE\u7B54\u95EE\u9898\u3002
|
|
4960
|
+
2. \u540C\u4E00\u4E8B\u9879\u51FA\u73B0\u591A\u4E2A\u7248\u672C\u65F6\uFF0C\u9ED8\u8BA4\u8F83\u65B0\u7684\u660E\u786E\u6D88\u606F\u4F18\u5148\u3002
|
|
4961
|
+
3. \u56DE\u7B54\u53EA\u5F15\u7528\u5B9E\u9645\u652F\u6491\u7ED3\u8BBA\u7684\u8BC1\u636E\u3002
|
|
4962
|
+
|
|
4963
|
+
\u68C0\u7D22\u8BC1\u636E\uFF1A
|
|
4964
|
+
${evidenceText}`
|
|
4965
|
+
}
|
|
4966
|
+
]
|
|
4967
|
+
};
|
|
4968
|
+
}
|
|
4969
|
+
async function generateGroundedAnswer(input2) {
|
|
4970
|
+
const prompt = buildEvidencePrompt(input2.question, input2.evidence);
|
|
4971
|
+
const answer = await input2.model.complete(prompt.messages);
|
|
4972
|
+
return {
|
|
4973
|
+
answer,
|
|
4974
|
+
citations: prompt.citations
|
|
4975
|
+
};
|
|
4976
|
+
}
|
|
4977
|
+
|
|
4462
4978
|
// src/rag/qa-service.ts
|
|
4463
4979
|
async function askWithRag(input2) {
|
|
4464
4980
|
const evidence = await input2.retriever.retrieve(input2.question);
|
|
@@ -4475,6 +4991,42 @@ async function askWithRag(input2) {
|
|
|
4475
4991
|
});
|
|
4476
4992
|
}
|
|
4477
4993
|
|
|
4994
|
+
// src/rag/citations.ts
|
|
4995
|
+
function isOpaqueId(value) {
|
|
4996
|
+
return Boolean(value && /^(ou|oc|om|cli|on|un|uid)_?[a-z0-9]+/i.test(value));
|
|
4997
|
+
}
|
|
4998
|
+
function formatTime(value) {
|
|
4999
|
+
if (!value) {
|
|
5000
|
+
return "\u672A\u77E5\u65F6\u95F4";
|
|
5001
|
+
}
|
|
5002
|
+
const date = new Date(value);
|
|
5003
|
+
if (Number.isNaN(date.getTime())) {
|
|
5004
|
+
return value;
|
|
5005
|
+
}
|
|
5006
|
+
const pad = (input2) => String(input2).padStart(2, "0");
|
|
5007
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
5008
|
+
}
|
|
5009
|
+
function formatSpeaker(source) {
|
|
5010
|
+
if (source.type === "file") {
|
|
5011
|
+
return isOpaqueId(source.label) ? "\u6587\u4EF6" : `\u6587\u4EF6 ${source.label}`;
|
|
5012
|
+
}
|
|
5013
|
+
if (source.sender && !isOpaqueId(source.sender)) {
|
|
5014
|
+
return source.sender;
|
|
5015
|
+
}
|
|
5016
|
+
return "\u7FA4\u6210\u5458";
|
|
5017
|
+
}
|
|
5018
|
+
function clipText(value, maxLength) {
|
|
5019
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
5020
|
+
return normalized.length > maxLength ? `${normalized.slice(0, maxLength)}...` : normalized;
|
|
5021
|
+
}
|
|
5022
|
+
function formatCitation(citation, options = {}) {
|
|
5023
|
+
const maxTextLength = options.maxTextLength ?? 120;
|
|
5024
|
+
const speaker = formatSpeaker(citation.source);
|
|
5025
|
+
const time = formatTime(citation.source.timestamp);
|
|
5026
|
+
const verb = citation.source.type === "file" ? "\u8BB0\u5F55" : "\u8BF4";
|
|
5027
|
+
return `[${citation.marker}] ${speaker}\u5728 ${time} ${verb}\uFF1A\u201C${clipText(citation.text, maxTextLength)}\u201D`;
|
|
5028
|
+
}
|
|
5029
|
+
|
|
4478
5030
|
// src/update/npm-updater.ts
|
|
4479
5031
|
import { execFile } from "child_process";
|
|
4480
5032
|
import { promisify } from "util";
|
|
@@ -4578,6 +5130,7 @@ async function updateChatterCatcher(options) {
|
|
|
4578
5130
|
}
|
|
4579
5131
|
|
|
4580
5132
|
// src/web/server.ts
|
|
5133
|
+
import crypto8 from "crypto";
|
|
4581
5134
|
import Fastify from "fastify";
|
|
4582
5135
|
function buildHtml() {
|
|
4583
5136
|
return `<!doctype html>
|
|
@@ -4725,6 +5278,10 @@ function buildHtml() {
|
|
|
4725
5278
|
<h2>\u89E3\u6790\u4EFB\u52A1</h2>
|
|
4726
5279
|
<div id="file-jobs" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
4727
5280
|
</section>
|
|
5281
|
+
<section>
|
|
5282
|
+
<h2>\u5B9A\u65F6\u4EFB\u52A1</h2>
|
|
5283
|
+
<div id="cron-jobs" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
5284
|
+
</section>
|
|
4728
5285
|
<section>
|
|
4729
5286
|
<h2>\u672C\u5730\u64CD\u4F5C</h2>
|
|
4730
5287
|
<p><code>chattercatcher settings</code> \u4FEE\u6539\u914D\u7F6E\u3002</p>
|
|
@@ -4741,10 +5298,13 @@ function buildHtml() {
|
|
|
4741
5298
|
const chats = document.querySelector("#chats");
|
|
4742
5299
|
const files = document.querySelector("#files");
|
|
4743
5300
|
const fileJobs = document.querySelector("#file-jobs");
|
|
5301
|
+
const cronJobs = document.querySelector("#cron-jobs");
|
|
4744
5302
|
const qaLogs = document.querySelector("#qa-logs");
|
|
4745
5303
|
const processMessages = document.querySelector("#process-messages");
|
|
4746
5304
|
const actionStatus = document.querySelector("#action-status");
|
|
4747
5305
|
|
|
5306
|
+
let webActionToken = "__WEB_ACTION_TOKEN__";
|
|
5307
|
+
|
|
4748
5308
|
function fmt(value) {
|
|
4749
5309
|
return value == null || value === "" ? "-" : String(value);
|
|
4750
5310
|
}
|
|
@@ -4933,6 +5493,36 @@ function buildHtml() {
|
|
|
4933
5493
|
\`;
|
|
4934
5494
|
}
|
|
4935
5495
|
|
|
5496
|
+
function renderCronJobs(items) {
|
|
5497
|
+
if (items.length === 0) {
|
|
5498
|
+
cronJobs.className = "empty";
|
|
5499
|
+
cronJobs.textContent = "\u8FD8\u6CA1\u6709\u5B9A\u65F6\u4EFB\u52A1\u3002\u53EF\u5728\u98DE\u4E66\u7FA4\u91CC @ \u673A\u5668\u4EBA\u521B\u5EFA\u3002";
|
|
5500
|
+
return;
|
|
5501
|
+
}
|
|
5502
|
+
cronJobs.className = "";
|
|
5503
|
+
cronJobs.innerHTML = \`
|
|
5504
|
+
<table>
|
|
5505
|
+
<thead><tr><th>\u4EFB\u52A1</th><th>\u72B6\u6001</th></tr></thead>
|
|
5506
|
+
<tbody>
|
|
5507
|
+
\${items.map((item) => \`
|
|
5508
|
+
<tr>
|
|
5509
|
+
<td>
|
|
5510
|
+
<div>\${escapeHtml(item.schedule)}</div>
|
|
5511
|
+
<div class="message" title="\${escapeHtml(item.prompt)}">\${escapeHtml(item.prompt)}</div>
|
|
5512
|
+
<div class="path" title="\${escapeHtml(item.id)}">ID: \${escapeHtml(item.id)}</div>
|
|
5513
|
+
<div class="path" title="\${escapeHtml(item.chatId)}">\u7FA4: \${escapeHtml(item.chatId)}</div>
|
|
5514
|
+
<div class="path">\u4E0B\u6B21: \${escapeHtml(formatDateTime(item.nextRunAt))}</div>
|
|
5515
|
+
<div class="path" title="\${escapeHtml(item.lastError || "")}">\${escapeHtml(item.lastError || "")}</div>
|
|
5516
|
+
\${item.status === "active" ? \`<button type="button" data-delete-cron-job="\${escapeHtml(item.id)}">\u5220\u9664</button>\` : ""}
|
|
5517
|
+
</td>
|
|
5518
|
+
<td>\${escapeHtml(item.status)}</td>
|
|
5519
|
+
</tr>
|
|
5520
|
+
\`).join("")}
|
|
5521
|
+
</tbody>
|
|
5522
|
+
</table>
|
|
5523
|
+
\`;
|
|
5524
|
+
}
|
|
5525
|
+
|
|
4936
5526
|
function renderQaLogs(items) {
|
|
4937
5527
|
if (items.length === 0) {
|
|
4938
5528
|
qaLogs.className = "empty";
|
|
@@ -4964,29 +5554,38 @@ function buildHtml() {
|
|
|
4964
5554
|
}
|
|
4965
5555
|
|
|
4966
5556
|
async function load() {
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
5557
|
+
try {
|
|
5558
|
+
const [status, recent, episodeList, chatList, fileList, jobList, qaLogList, cronJobList] = await Promise.all([
|
|
5559
|
+
fetch("/api/status").then((response) => response.json()),
|
|
5560
|
+
fetch("/api/messages/recent?limit=20").then((response) => response.json()),
|
|
5561
|
+
fetch("/api/episodes?limit=10").then((response) => response.json()),
|
|
5562
|
+
fetch("/api/chats").then((response) => response.json()),
|
|
5563
|
+
fetch("/api/files").then((response) => response.json()),
|
|
5564
|
+
fetch("/api/file-jobs").then((response) => response.json()),
|
|
5565
|
+
fetch("/api/qa-logs?limit=10").then((response) => response.json()),
|
|
5566
|
+
fetch("/api/cron-jobs").then((response) => response.json()),
|
|
5567
|
+
]);
|
|
5568
|
+
renderMetrics(status);
|
|
5569
|
+
renderMessages(recent.items);
|
|
5570
|
+
renderEpisodes(episodeList.items);
|
|
5571
|
+
renderChats(chatList.items);
|
|
5572
|
+
renderFiles(fileList.items);
|
|
5573
|
+
renderFileJobs(jobList.items);
|
|
5574
|
+
renderQaLogs(qaLogList.items);
|
|
5575
|
+
renderCronJobs(cronJobList.items);
|
|
5576
|
+
} catch (error) {
|
|
5577
|
+
metrics.innerHTML = '<div class="empty">\u6570\u636E\u52A0\u8F7D\u5931\u8D25\uFF1A' + escapeHtml(error instanceof Error ? error.message : String(error)) + '</div>';
|
|
5578
|
+
}
|
|
4983
5579
|
}
|
|
4984
5580
|
|
|
4985
5581
|
async function processNow() {
|
|
4986
5582
|
processMessages.disabled = true;
|
|
4987
5583
|
actionStatus.textContent = "\u6B63\u5728\u5904\u7406\u6D88\u606F\u7D22\u5F15...";
|
|
4988
5584
|
try {
|
|
4989
|
-
const response = await fetch("/api/process/messages", {
|
|
5585
|
+
const response = await fetch("/api/process/messages", {
|
|
5586
|
+
method: "POST",
|
|
5587
|
+
headers: { "x-chattercatcher-web-token": webActionToken },
|
|
5588
|
+
});
|
|
4990
5589
|
const result = await response.json();
|
|
4991
5590
|
if (!response.ok) {
|
|
4992
5591
|
actionStatus.textContent = result.message || "\u5904\u7406\u5931\u8D25\u3002";
|
|
@@ -5006,6 +5605,28 @@ function buildHtml() {
|
|
|
5006
5605
|
}
|
|
5007
5606
|
}
|
|
5008
5607
|
|
|
5608
|
+
document.addEventListener("click", async (event) => {
|
|
5609
|
+
const target = event.target;
|
|
5610
|
+
if (!(target instanceof HTMLElement)) return;
|
|
5611
|
+
const id = target.dataset.deleteCronJob;
|
|
5612
|
+
if (!id) return;
|
|
5613
|
+
target.setAttribute("disabled", "disabled");
|
|
5614
|
+
actionStatus.textContent = "\u6B63\u5728\u5220\u9664\u5B9A\u65F6\u4EFB\u52A1...";
|
|
5615
|
+
try {
|
|
5616
|
+
const response = await fetch(\`/api/cron-jobs/\${encodeURIComponent(id)}\`, {
|
|
5617
|
+
method: "DELETE",
|
|
5618
|
+
headers: { "x-chattercatcher-web-token": webActionToken },
|
|
5619
|
+
});
|
|
5620
|
+
const result = await response.json();
|
|
5621
|
+
actionStatus.textContent = result.ok ? "\u5B9A\u65F6\u4EFB\u52A1\u5DF2\u5220\u9664\u3002" : result.message || "\u5220\u9664\u5931\u8D25\u3002";
|
|
5622
|
+
await load();
|
|
5623
|
+
} catch (error) {
|
|
5624
|
+
actionStatus.textContent = error instanceof Error ? error.message : String(error);
|
|
5625
|
+
} finally {
|
|
5626
|
+
target.removeAttribute("disabled");
|
|
5627
|
+
}
|
|
5628
|
+
});
|
|
5629
|
+
|
|
5009
5630
|
processMessages.addEventListener("click", () => void processNow());
|
|
5010
5631
|
void load();
|
|
5011
5632
|
setInterval(() => {
|
|
@@ -5021,6 +5642,16 @@ function parseLimit(value, fallback, max) {
|
|
|
5021
5642
|
const rawLimit = Number(value ?? fallback);
|
|
5022
5643
|
return Number.isFinite(rawLimit) ? Math.min(Math.max(Math.trunc(rawLimit), 1), max) : fallback;
|
|
5023
5644
|
}
|
|
5645
|
+
function getWebActionToken(secrets) {
|
|
5646
|
+
return secrets.web.actionToken;
|
|
5647
|
+
}
|
|
5648
|
+
function readHeader(value) {
|
|
5649
|
+
return Array.isArray(value) ? value[0] : value;
|
|
5650
|
+
}
|
|
5651
|
+
function isAuthorizedWebAction(request, token) {
|
|
5652
|
+
const provided = readHeader(request.headers["x-chattercatcher-web-token"]);
|
|
5653
|
+
return provided === token;
|
|
5654
|
+
}
|
|
5024
5655
|
function createWebApp(config) {
|
|
5025
5656
|
const app = Fastify({ logger: false });
|
|
5026
5657
|
const database = openDatabase(config);
|
|
@@ -5028,30 +5659,44 @@ function createWebApp(config) {
|
|
|
5028
5659
|
const episodes = new EpisodeRepository(database);
|
|
5029
5660
|
const fileJobs = new FileJobRepository(database);
|
|
5030
5661
|
const qaLogs = new QaLogRepository(database);
|
|
5662
|
+
const cronJobs = new CronJobRepository(database);
|
|
5663
|
+
let webActionToken = "";
|
|
5664
|
+
const tokenReady = (async () => {
|
|
5665
|
+
const secrets = await loadSecrets();
|
|
5666
|
+
if (!secrets.web.actionToken) {
|
|
5667
|
+
secrets.web.actionToken = crypto8.randomBytes(32).toString("hex");
|
|
5668
|
+
await saveSecrets(secrets);
|
|
5669
|
+
}
|
|
5670
|
+
webActionToken = getWebActionToken(secrets);
|
|
5671
|
+
})();
|
|
5031
5672
|
app.addHook("onClose", async () => {
|
|
5032
5673
|
database.close();
|
|
5033
5674
|
});
|
|
5034
|
-
app.get("/api/status", async () =>
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5675
|
+
app.get("/api/status", async () => {
|
|
5676
|
+
await tokenReady;
|
|
5677
|
+
return {
|
|
5678
|
+
app: "ChatterCatcher",
|
|
5679
|
+
gateway: getGatewayStatus(config),
|
|
5680
|
+
data: {
|
|
5681
|
+
chats: messages.getChatCount(),
|
|
5682
|
+
messages: messages.getMessageCount(),
|
|
5683
|
+
episodes: episodes.getEpisodeCount(),
|
|
5684
|
+
files: messages.listFiles(1e3).length,
|
|
5685
|
+
qaLogs: qaLogs.getCount(),
|
|
5686
|
+
cronJobs: cronJobs.list(1e3).length
|
|
5687
|
+
},
|
|
5688
|
+
rag: {
|
|
5689
|
+
mode: "required",
|
|
5690
|
+
note: "\u95EE\u7B54\u5FC5\u987B\u5148\u68C0\u7D22\u8BC1\u636E\uFF0C\u7981\u6B62\u5168\u91CF\u4E0A\u4E0B\u6587\u5806\u53E0\u3002",
|
|
5691
|
+
retrieval: {
|
|
5692
|
+
keyword: "SQLite FTS5",
|
|
5693
|
+
vector: "SQLite embedding",
|
|
5694
|
+
hybrid: true
|
|
5695
|
+
}
|
|
5696
|
+
},
|
|
5697
|
+
web: config.web
|
|
5698
|
+
};
|
|
5699
|
+
});
|
|
5055
5700
|
app.get("/api/chats", async () => ({
|
|
5056
5701
|
items: messages.listChats()
|
|
5057
5702
|
}));
|
|
@@ -5086,7 +5731,33 @@ function createWebApp(config) {
|
|
|
5086
5731
|
items: qaLogs.listRecent(limit)
|
|
5087
5732
|
};
|
|
5088
5733
|
});
|
|
5089
|
-
app.
|
|
5734
|
+
app.get("/api/cron-jobs", async (request) => {
|
|
5735
|
+
const limit = parseLimit(request.query.limit, 50, 200);
|
|
5736
|
+
return {
|
|
5737
|
+
items: cronJobs.list(limit)
|
|
5738
|
+
};
|
|
5739
|
+
});
|
|
5740
|
+
app.delete("/api/cron-jobs/:id", async (request, reply) => {
|
|
5741
|
+
await tokenReady;
|
|
5742
|
+
if (!isAuthorizedWebAction(request, webActionToken)) {
|
|
5743
|
+
reply.code(403);
|
|
5744
|
+
return { ok: false, message: "Web \u64CD\u4F5C\u672A\u6388\u6743\u3002" };
|
|
5745
|
+
}
|
|
5746
|
+
const id = request.params.id;
|
|
5747
|
+
const job = cronJobs.get(id);
|
|
5748
|
+
if (!job) {
|
|
5749
|
+
reply.code(404);
|
|
5750
|
+
return { ok: false, message: "\u6CA1\u6709\u627E\u5230\u5B9A\u65F6\u4EFB\u52A1\u3002" };
|
|
5751
|
+
}
|
|
5752
|
+
const ok = cronJobs.deleteByChat(id, job.chatId);
|
|
5753
|
+
return { ok };
|
|
5754
|
+
});
|
|
5755
|
+
app.post("/api/process/messages", async (request, reply) => {
|
|
5756
|
+
await tokenReady;
|
|
5757
|
+
if (!isAuthorizedWebAction(request, webActionToken)) {
|
|
5758
|
+
reply.code(403);
|
|
5759
|
+
return { status: "failed", message: "Web \u64CD\u4F5C\u672A\u6388\u6743\u3002" };
|
|
5760
|
+
}
|
|
5090
5761
|
try {
|
|
5091
5762
|
return await processMessagesNow({
|
|
5092
5763
|
config,
|
|
@@ -5103,8 +5774,9 @@ function createWebApp(config) {
|
|
|
5103
5774
|
}
|
|
5104
5775
|
});
|
|
5105
5776
|
app.get("/", async (_request, reply) => {
|
|
5777
|
+
await tokenReady;
|
|
5106
5778
|
reply.type("text/html; charset=utf-8");
|
|
5107
|
-
return buildHtml();
|
|
5779
|
+
return buildHtml().replaceAll("__WEB_ACTION_TOKEN__", webActionToken);
|
|
5108
5780
|
});
|
|
5109
5781
|
return app;
|
|
5110
5782
|
}
|
|
@@ -5301,6 +5973,8 @@ async function startGatewayForegroundCommand() {
|
|
|
5301
5973
|
mode: "gateway"
|
|
5302
5974
|
});
|
|
5303
5975
|
const database = openDatabase(config);
|
|
5976
|
+
const chatModel = createChatModel(config, secrets);
|
|
5977
|
+
const sender = FeishuMessageSender.fromConfig(config, secrets);
|
|
5304
5978
|
const vectorStore = hasEmbeddingConfig(config, secrets) ? new SqliteVectorStore(database, { model: config.embedding.model }) : null;
|
|
5305
5979
|
const gatewayRuntime = createFeishuGateway({
|
|
5306
5980
|
config,
|
|
@@ -5315,7 +5989,7 @@ async function startGatewayForegroundCommand() {
|
|
|
5315
5989
|
}) : void 0,
|
|
5316
5990
|
episodeProcessor: {
|
|
5317
5991
|
database,
|
|
5318
|
-
model:
|
|
5992
|
+
model: chatModel
|
|
5319
5993
|
},
|
|
5320
5994
|
imageMultimodalProcessor: config.multimodal.baseUrl && config.multimodal.model && secrets.multimodal.apiKey ? {
|
|
5321
5995
|
database,
|
|
@@ -5324,12 +5998,17 @@ async function startGatewayForegroundCommand() {
|
|
|
5324
5998
|
indexingProcessor: {
|
|
5325
5999
|
database
|
|
5326
6000
|
},
|
|
6001
|
+
cronJobProcessor: {
|
|
6002
|
+
database,
|
|
6003
|
+
model: chatModel,
|
|
6004
|
+
sender
|
|
6005
|
+
},
|
|
5327
6006
|
questionHandler: new FeishuQuestionHandler({
|
|
5328
6007
|
config,
|
|
5329
6008
|
secrets,
|
|
5330
6009
|
database,
|
|
5331
|
-
sender
|
|
5332
|
-
model:
|
|
6010
|
+
sender,
|
|
6011
|
+
model: chatModel
|
|
5333
6012
|
})
|
|
5334
6013
|
});
|
|
5335
6014
|
const cleanup = () => {
|