chattercatcher 0.1.15 → 0.1.17
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 +334 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +48 -4
- package/dist/index.js +332 -11
- package/dist/index.js.map +1 -1
- package/docs/superpowers/plans/2026-05-02-agentic-rag.md +1013 -0
- package/docs/superpowers/specs/2026-05-02-agentic-rag-design.md +143 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs13 from "fs/promises";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "chattercatcher",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.17",
|
|
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",
|
|
@@ -36,6 +36,7 @@ var package_default = {
|
|
|
36
36
|
},
|
|
37
37
|
scripts: {
|
|
38
38
|
build: "tsup",
|
|
39
|
+
prepack: "npm run build",
|
|
39
40
|
dev: "tsx src/cli.ts",
|
|
40
41
|
lint: "tsc --noEmit",
|
|
41
42
|
typecheck: "tsc --noEmit",
|
|
@@ -52,7 +53,7 @@ var package_default = {
|
|
|
52
53
|
license: "MIT",
|
|
53
54
|
dependencies: {
|
|
54
55
|
"@inquirer/prompts": "^8.4.2",
|
|
55
|
-
"@larksuiteoapi/node-sdk": "^1.62.
|
|
56
|
+
"@larksuiteoapi/node-sdk": "^1.62.1",
|
|
56
57
|
"better-sqlite3": "^12.9.0",
|
|
57
58
|
commander: "^14.0.3",
|
|
58
59
|
fastify: "^5.8.5",
|
|
@@ -114,7 +115,7 @@ var appConfigSchema = z.object({
|
|
|
114
115
|
episodes: z.object({
|
|
115
116
|
windowMinutes: z.number().int().positive().default(10),
|
|
116
117
|
quietMinutes: z.number().int().positive().default(2)
|
|
117
|
-
})
|
|
118
|
+
}).default({ windowMinutes: 10, quietMinutes: 2 })
|
|
118
119
|
});
|
|
119
120
|
var appSecretsSchema = z.object({
|
|
120
121
|
feishu: z.object({
|
|
@@ -893,6 +894,40 @@ function getGatewayStatus(config, secrets) {
|
|
|
893
894
|
function normalizeBaseUrl(baseUrl) {
|
|
894
895
|
return baseUrl.replace(/\/+$/, "");
|
|
895
896
|
}
|
|
897
|
+
function toOpenAIMessage(message) {
|
|
898
|
+
return {
|
|
899
|
+
role: message.role,
|
|
900
|
+
content: message.content,
|
|
901
|
+
...message.toolCallId ? { tool_call_id: message.toolCallId } : {},
|
|
902
|
+
...message.toolCalls ? {
|
|
903
|
+
tool_calls: message.toolCalls.map((toolCall) => ({
|
|
904
|
+
id: toolCall.id,
|
|
905
|
+
type: "function",
|
|
906
|
+
function: {
|
|
907
|
+
name: toolCall.name,
|
|
908
|
+
arguments: JSON.stringify(toolCall.input)
|
|
909
|
+
}
|
|
910
|
+
}))
|
|
911
|
+
} : {}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function toOpenAITool(tool) {
|
|
915
|
+
return {
|
|
916
|
+
type: "function",
|
|
917
|
+
function: {
|
|
918
|
+
name: tool.name,
|
|
919
|
+
description: tool.description,
|
|
920
|
+
parameters: tool.inputSchema
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function parseToolCalls(message) {
|
|
925
|
+
return message?.tool_calls?.map((toolCall) => ({
|
|
926
|
+
id: toolCall.id,
|
|
927
|
+
name: toolCall.function.name,
|
|
928
|
+
input: JSON.parse(toolCall.function.arguments)
|
|
929
|
+
})) ?? [];
|
|
930
|
+
}
|
|
896
931
|
var OpenAICompatibleChatModel = class {
|
|
897
932
|
constructor(options) {
|
|
898
933
|
this.options = options;
|
|
@@ -910,7 +945,7 @@ var OpenAICompatibleChatModel = class {
|
|
|
910
945
|
},
|
|
911
946
|
body: JSON.stringify({
|
|
912
947
|
model: this.options.model,
|
|
913
|
-
messages,
|
|
948
|
+
messages: messages.map(toOpenAIMessage),
|
|
914
949
|
temperature: this.options.temperature ?? 0.2
|
|
915
950
|
})
|
|
916
951
|
});
|
|
@@ -925,6 +960,35 @@ var OpenAICompatibleChatModel = class {
|
|
|
925
960
|
}
|
|
926
961
|
return content;
|
|
927
962
|
}
|
|
963
|
+
async completeWithTools(messages, tools) {
|
|
964
|
+
if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {
|
|
965
|
+
throw new Error("LLM \u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
|
|
966
|
+
}
|
|
967
|
+
const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/chat/completions`, {
|
|
968
|
+
method: "POST",
|
|
969
|
+
headers: {
|
|
970
|
+
authorization: `Bearer ${this.options.apiKey}`,
|
|
971
|
+
"content-type": "application/json"
|
|
972
|
+
},
|
|
973
|
+
body: JSON.stringify({
|
|
974
|
+
model: this.options.model,
|
|
975
|
+
messages: messages.map(toOpenAIMessage),
|
|
976
|
+
tools: tools.map(toOpenAITool),
|
|
977
|
+
tool_choice: "auto",
|
|
978
|
+
temperature: this.options.temperature ?? 0.2
|
|
979
|
+
})
|
|
980
|
+
});
|
|
981
|
+
if (!response.ok) {
|
|
982
|
+
const body = await response.text();
|
|
983
|
+
throw new Error(`LLM \u8BF7\u6C42\u5931\u8D25\uFF1A${response.status} ${body}`);
|
|
984
|
+
}
|
|
985
|
+
const data2 = await response.json();
|
|
986
|
+
const message = data2.choices?.[0]?.message;
|
|
987
|
+
return {
|
|
988
|
+
content: message?.content ?? "",
|
|
989
|
+
toolCalls: parseToolCalls(message)
|
|
990
|
+
};
|
|
991
|
+
}
|
|
928
992
|
};
|
|
929
993
|
var OpenAICompatibleEmbeddingModel = class {
|
|
930
994
|
constructor(options) {
|
|
@@ -1443,6 +1507,29 @@ var EpisodeRepository = class {
|
|
|
1443
1507
|
messageIds: window.messages.map((message) => message.id)
|
|
1444
1508
|
};
|
|
1445
1509
|
}
|
|
1510
|
+
getEpisodeCount() {
|
|
1511
|
+
const row = this.database.prepare("SELECT count(*) AS count FROM memory_episodes").get();
|
|
1512
|
+
return row.count;
|
|
1513
|
+
}
|
|
1514
|
+
listRecentEpisodes(limit = 20) {
|
|
1515
|
+
return this.database.prepare(
|
|
1516
|
+
`
|
|
1517
|
+
SELECT
|
|
1518
|
+
e.id,
|
|
1519
|
+
e.chat_id AS chatId,
|
|
1520
|
+
c.name AS chatName,
|
|
1521
|
+
e.summary,
|
|
1522
|
+
e.message_count AS messageCount,
|
|
1523
|
+
e.started_at AS startedAt,
|
|
1524
|
+
e.ended_at AS endedAt,
|
|
1525
|
+
e.created_at AS createdAt
|
|
1526
|
+
FROM memory_episodes e
|
|
1527
|
+
JOIN chats c ON c.id = e.chat_id
|
|
1528
|
+
ORDER BY e.ended_at DESC
|
|
1529
|
+
LIMIT ?
|
|
1530
|
+
`
|
|
1531
|
+
).all(limit);
|
|
1532
|
+
}
|
|
1446
1533
|
searchEpisodes(query, limit = 8) {
|
|
1447
1534
|
const ftsQuery = escapeFtsQuery2(query);
|
|
1448
1535
|
return this.database.prepare(
|
|
@@ -1588,6 +1675,73 @@ var MessageFtsRetriever = class {
|
|
|
1588
1675
|
}
|
|
1589
1676
|
};
|
|
1590
1677
|
|
|
1678
|
+
// src/rag/search-tools.ts
|
|
1679
|
+
var searchInputSchema = {
|
|
1680
|
+
type: "object",
|
|
1681
|
+
properties: {
|
|
1682
|
+
query: { type: "string", description: "Search query written by the model." },
|
|
1683
|
+
limit: { type: "number", description: "Maximum number of evidence blocks to return." }
|
|
1684
|
+
},
|
|
1685
|
+
required: ["query"],
|
|
1686
|
+
additionalProperties: false
|
|
1687
|
+
};
|
|
1688
|
+
function parseSearchInput(input2) {
|
|
1689
|
+
const rawQuery = typeof input2 === "object" && input2 !== null && "query" in input2 ? input2.query : void 0;
|
|
1690
|
+
if (typeof rawQuery !== "string") {
|
|
1691
|
+
throw new Error("\u641C\u7D22 query \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u3002");
|
|
1692
|
+
}
|
|
1693
|
+
const query = rawQuery.trim();
|
|
1694
|
+
if (!query) {
|
|
1695
|
+
throw new Error("\u641C\u7D22 query \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u3002");
|
|
1696
|
+
}
|
|
1697
|
+
const rawLimit = typeof input2 === "object" && input2 !== null && "limit" in input2 ? input2.limit : void 0;
|
|
1698
|
+
const numericLimit = typeof rawLimit === "number" && Number.isFinite(rawLimit) ? rawLimit : 5;
|
|
1699
|
+
const limit = Math.min(12, Math.max(1, Math.floor(numericLimit)));
|
|
1700
|
+
return { query, limit };
|
|
1701
|
+
}
|
|
1702
|
+
async function runRetriever(retriever, input2) {
|
|
1703
|
+
const { query, limit } = parseSearchInput(input2);
|
|
1704
|
+
const results = await retriever.retrieve(query);
|
|
1705
|
+
return results.slice(0, limit);
|
|
1706
|
+
}
|
|
1707
|
+
function createSearchTool(name, description, retriever) {
|
|
1708
|
+
return {
|
|
1709
|
+
name,
|
|
1710
|
+
description,
|
|
1711
|
+
inputSchema: searchInputSchema,
|
|
1712
|
+
execute: (input2) => runRetriever(retriever, input2)
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
function createRagSearchTools(input2) {
|
|
1716
|
+
const tools = [
|
|
1717
|
+
createSearchTool(
|
|
1718
|
+
"hybrid_search",
|
|
1719
|
+
"Search across all indexed RAG evidence using the default hybrid retrieval strategy.",
|
|
1720
|
+
input2.hybrid
|
|
1721
|
+
),
|
|
1722
|
+
createSearchTool(
|
|
1723
|
+
"search_messages",
|
|
1724
|
+
"Search chat messages only when the answer likely depends on message-level evidence.",
|
|
1725
|
+
input2.messages
|
|
1726
|
+
),
|
|
1727
|
+
createSearchTool(
|
|
1728
|
+
"search_episodes",
|
|
1729
|
+
"Search episode summaries only when the answer likely depends on longer-running context.",
|
|
1730
|
+
input2.episodes
|
|
1731
|
+
)
|
|
1732
|
+
];
|
|
1733
|
+
if (input2.semantic) {
|
|
1734
|
+
tools.push(
|
|
1735
|
+
createSearchTool(
|
|
1736
|
+
"semantic_search",
|
|
1737
|
+
"Search semantic vector evidence only when broader conceptual recall is needed.",
|
|
1738
|
+
input2.semantic
|
|
1739
|
+
)
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
return tools;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1591
1745
|
// src/rag/embedding.ts
|
|
1592
1746
|
function cosineSimilarity(left, right) {
|
|
1593
1747
|
if (left.length === 0 || right.length === 0 || left.length !== right.length) {
|
|
@@ -1742,6 +1896,20 @@ async function createHybridRetriever(input2) {
|
|
|
1742
1896
|
}
|
|
1743
1897
|
};
|
|
1744
1898
|
}
|
|
1899
|
+
async function createAgenticRagSearchTools(input2) {
|
|
1900
|
+
const episodes = new EpisodeFtsRetriever(new EpisodeRepository(input2.database));
|
|
1901
|
+
const messages = new MessageFtsRetriever(input2.messages, { excludeMessageIds: input2.excludeMessageIds });
|
|
1902
|
+
const semantic = hasEmbeddingConfig(input2.config, input2.secrets) ? new VectorRetriever(
|
|
1903
|
+
createEmbeddingModel(input2.config, input2.secrets),
|
|
1904
|
+
new SqliteVectorStore(input2.database, { model: input2.config.embedding.model })
|
|
1905
|
+
) : void 0;
|
|
1906
|
+
const hybrid = new HybridRetriever(semantic ? [episodes, messages, semantic] : [episodes, messages]);
|
|
1907
|
+
return {
|
|
1908
|
+
tools: createRagSearchTools({ hybrid, messages, episodes, semantic }),
|
|
1909
|
+
close: () => {
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1745
1913
|
|
|
1746
1914
|
// src/doctor/checks.ts
|
|
1747
1915
|
function pass(name, message) {
|
|
@@ -2430,12 +2598,99 @@ async function generateGroundedAnswer(input2) {
|
|
|
2430
2598
|
};
|
|
2431
2599
|
}
|
|
2432
2600
|
|
|
2433
|
-
// src/rag/qa-service.ts
|
|
2434
|
-
|
|
2435
|
-
|
|
2601
|
+
// src/rag/agentic-qa-service.ts
|
|
2602
|
+
var DEFAULT_MAX_MODEL_TURNS = 4;
|
|
2603
|
+
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
2604
|
+
var DEFAULT_MAX_EVIDENCE = 12;
|
|
2605
|
+
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";
|
|
2606
|
+
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";
|
|
2607
|
+
function toToolResultContent(results) {
|
|
2608
|
+
return JSON.stringify(
|
|
2609
|
+
results.map((item) => ({
|
|
2610
|
+
id: item.id,
|
|
2611
|
+
text: item.text,
|
|
2612
|
+
score: item.score,
|
|
2613
|
+
source: item.source
|
|
2614
|
+
}))
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
function toToolErrorContent(message) {
|
|
2618
|
+
return JSON.stringify({ error: message });
|
|
2619
|
+
}
|
|
2620
|
+
function dedupeEvidence(evidence, maxEvidence) {
|
|
2621
|
+
const deduped = [];
|
|
2622
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2623
|
+
for (const item of evidence) {
|
|
2624
|
+
if (seen.has(item.id)) {
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2627
|
+
seen.add(item.id);
|
|
2628
|
+
deduped.push(item);
|
|
2629
|
+
if (deduped.length >= maxEvidence) {
|
|
2630
|
+
break;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
return deduped;
|
|
2634
|
+
}
|
|
2635
|
+
async function askWithAgenticRag(input2) {
|
|
2636
|
+
if (!input2.model.completeWithTools) {
|
|
2637
|
+
throw new Error("\u5F53\u524D LLM \u5BA2\u6237\u7AEF\u4E0D\u652F\u6301\u5DE5\u5177\u8C03\u7528\u3002");
|
|
2638
|
+
}
|
|
2639
|
+
const maxModelTurns = input2.maxModelTurns ?? DEFAULT_MAX_MODEL_TURNS;
|
|
2640
|
+
const maxToolCalls = input2.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
2641
|
+
const maxEvidence = input2.maxEvidence ?? DEFAULT_MAX_EVIDENCE;
|
|
2642
|
+
const messages = [
|
|
2643
|
+
{ role: "system", content: AGENTIC_SYSTEM_PROMPT },
|
|
2644
|
+
{ role: "user", content: input2.question }
|
|
2645
|
+
];
|
|
2646
|
+
const toolsByName = new Map(input2.tools.map((tool) => [tool.name, tool]));
|
|
2647
|
+
let evidence = [];
|
|
2648
|
+
let toolCallsUsed = 0;
|
|
2649
|
+
for (let turn = 0; turn < maxModelTurns; turn += 1) {
|
|
2650
|
+
const assistantResult = await input2.model.completeWithTools(messages, input2.tools);
|
|
2651
|
+
messages.push({
|
|
2652
|
+
role: "assistant",
|
|
2653
|
+
content: assistantResult.content,
|
|
2654
|
+
toolCalls: assistantResult.toolCalls
|
|
2655
|
+
});
|
|
2656
|
+
if (assistantResult.toolCalls.length === 0) {
|
|
2657
|
+
break;
|
|
2658
|
+
}
|
|
2659
|
+
for (const toolCall of assistantResult.toolCalls) {
|
|
2660
|
+
if (toolCallsUsed >= maxToolCalls) {
|
|
2661
|
+
break;
|
|
2662
|
+
}
|
|
2663
|
+
toolCallsUsed += 1;
|
|
2664
|
+
const tool = toolsByName.get(toolCall.name);
|
|
2665
|
+
if (!tool) {
|
|
2666
|
+
messages.push({
|
|
2667
|
+
role: "tool",
|
|
2668
|
+
toolCallId: toolCall.id,
|
|
2669
|
+
content: toToolErrorContent(`\u672A\u77E5\u5DE5\u5177\uFF1A${toolCall.name}`)
|
|
2670
|
+
});
|
|
2671
|
+
continue;
|
|
2672
|
+
}
|
|
2673
|
+
try {
|
|
2674
|
+
const results = await tool.execute(toolCall.input);
|
|
2675
|
+
evidence = dedupeEvidence([...evidence, ...results], maxEvidence);
|
|
2676
|
+
messages.push({
|
|
2677
|
+
role: "tool",
|
|
2678
|
+
toolCallId: toolCall.id,
|
|
2679
|
+
content: toToolResultContent(results)
|
|
2680
|
+
});
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2683
|
+
messages.push({
|
|
2684
|
+
role: "tool",
|
|
2685
|
+
toolCallId: toolCall.id,
|
|
2686
|
+
content: toToolErrorContent(message)
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2436
2691
|
if (evidence.length === 0) {
|
|
2437
2692
|
return {
|
|
2438
|
-
answer:
|
|
2693
|
+
answer: NO_EVIDENCE_ANSWER,
|
|
2439
2694
|
citations: []
|
|
2440
2695
|
};
|
|
2441
2696
|
}
|
|
@@ -2553,7 +2808,7 @@ var FeishuQuestionHandler = class {
|
|
|
2553
2808
|
}
|
|
2554
2809
|
const questionMessageId = payload.event?.message?.message_id;
|
|
2555
2810
|
await this.acknowledgeQuestion(decision.chatId, questionMessageId);
|
|
2556
|
-
const {
|
|
2811
|
+
const { tools, close } = await createAgenticRagSearchTools({
|
|
2557
2812
|
config: this.options.config,
|
|
2558
2813
|
secrets: this.options.secrets,
|
|
2559
2814
|
database: this.options.database,
|
|
@@ -2562,9 +2817,9 @@ var FeishuQuestionHandler = class {
|
|
|
2562
2817
|
});
|
|
2563
2818
|
try {
|
|
2564
2819
|
try {
|
|
2565
|
-
const result = await
|
|
2820
|
+
const result = await askWithAgenticRag({
|
|
2566
2821
|
question: decision.question,
|
|
2567
|
-
|
|
2822
|
+
tools,
|
|
2568
2823
|
model: this.options.model
|
|
2569
2824
|
});
|
|
2570
2825
|
const citations = formatCitations(result.citations);
|
|
@@ -2668,6 +2923,13 @@ function assertFeishuConfig(config, secrets) {
|
|
|
2668
2923
|
throw new Error("\u98DE\u4E66\u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u5148\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
|
|
2669
2924
|
}
|
|
2670
2925
|
}
|
|
2926
|
+
function formatGatewayStartError(error) {
|
|
2927
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2928
|
+
if (message.includes("PingInterval") || message.includes("system busy") || message.includes("1000040345")) {
|
|
2929
|
+
return new Error(`\u98DE\u4E66\u957F\u8FDE\u63A5\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5 App ID / App Secret \u662F\u5426\u6B63\u786E\uFF1B\u539F\u59CB\u9519\u8BEF\uFF1A${message}`);
|
|
2930
|
+
}
|
|
2931
|
+
return error instanceof Error ? error : new Error(message);
|
|
2932
|
+
}
|
|
2671
2933
|
function createFeishuEventDispatcher(options) {
|
|
2672
2934
|
const answeredMessageIds = /* @__PURE__ */ new Set();
|
|
2673
2935
|
return new lark2.EventDispatcher({}).register({
|
|
@@ -2772,7 +3034,11 @@ function createFeishuGateway(options) {
|
|
|
2772
3034
|
});
|
|
2773
3035
|
return {
|
|
2774
3036
|
async start() {
|
|
2775
|
-
|
|
3037
|
+
try {
|
|
3038
|
+
await wsClient.start({ eventDispatcher });
|
|
3039
|
+
} catch (error) {
|
|
3040
|
+
throw formatGatewayStartError(error);
|
|
3041
|
+
}
|
|
2776
3042
|
},
|
|
2777
3043
|
stop() {
|
|
2778
3044
|
wsClient.close({ force: true });
|
|
@@ -3398,6 +3664,22 @@ async function processMessagesNow(input2) {
|
|
|
3398
3664
|
};
|
|
3399
3665
|
}
|
|
3400
3666
|
|
|
3667
|
+
// src/rag/qa-service.ts
|
|
3668
|
+
async function askWithRag(input2) {
|
|
3669
|
+
const evidence = await input2.retriever.retrieve(input2.question);
|
|
3670
|
+
if (evidence.length === 0) {
|
|
3671
|
+
return {
|
|
3672
|
+
answer: "\u4E0D\u77E5\u9053\u3002\u5F53\u524D\u672C\u5730\u77E5\u8BC6\u5E93\u6CA1\u6709\u68C0\u7D22\u5230\u8DB3\u591F\u8BC1\u636E\u3002",
|
|
3673
|
+
citations: []
|
|
3674
|
+
};
|
|
3675
|
+
}
|
|
3676
|
+
return generateGroundedAnswer({
|
|
3677
|
+
question: input2.question,
|
|
3678
|
+
evidence,
|
|
3679
|
+
model: input2.model
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3401
3683
|
// src/update/npm-updater.ts
|
|
3402
3684
|
import { execFile } from "child_process";
|
|
3403
3685
|
import { promisify } from "util";
|
|
@@ -3626,6 +3908,10 @@ function buildHtml() {
|
|
|
3626
3908
|
<h2>\u6700\u8FD1\u6D88\u606F</h2>
|
|
3627
3909
|
<div id="messages" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
3628
3910
|
</section>
|
|
3911
|
+
<section>
|
|
3912
|
+
<h2>\u4F1A\u8BDD\u8BB0\u5FC6</h2>
|
|
3913
|
+
<div id="episodes" class="empty">\u6B63\u5728\u8BFB\u53D6...</div>
|
|
3914
|
+
</section>
|
|
3629
3915
|
</div>
|
|
3630
3916
|
<aside>
|
|
3631
3917
|
<section>
|
|
@@ -3652,6 +3938,7 @@ function buildHtml() {
|
|
|
3652
3938
|
<script>
|
|
3653
3939
|
const metrics = document.querySelector("#metrics");
|
|
3654
3940
|
const messages = document.querySelector("#messages");
|
|
3941
|
+
const episodes = document.querySelector("#episodes");
|
|
3655
3942
|
const chats = document.querySelector("#chats");
|
|
3656
3943
|
const files = document.querySelector("#files");
|
|
3657
3944
|
const fileJobs = document.querySelector("#file-jobs");
|
|
@@ -3715,6 +4002,7 @@ function buildHtml() {
|
|
|
3715
4002
|
["Gateway", formatGatewayValue(status.gateway), formatGatewayNote(status.gateway), gatewayClass],
|
|
3716
4003
|
["\u7FA4\u804A", status.data.chats, "\u672C\u5730\u7FA4\u804A\u6570", ""],
|
|
3717
4004
|
["\u6D88\u606F", status.data.messages, "\u5DF2\u5165\u5E93\u6D88\u606F", ""],
|
|
4005
|
+
["\u4F1A\u8BDD\u8BB0\u5FC6", status.data.episodes, "\u5DF2\u751F\u6210\u6458\u8981", ""],
|
|
3718
4006
|
["\u6587\u4EF6", status.data.files, "\u6587\u4EF6\u77E5\u8BC6\u6E90", ""],
|
|
3719
4007
|
].map(([label, value, note, extra]) => \`
|
|
3720
4008
|
<div class="metric">
|
|
@@ -3748,6 +4036,29 @@ function buildHtml() {
|
|
|
3748
4036
|
\`;
|
|
3749
4037
|
}
|
|
3750
4038
|
|
|
4039
|
+
function renderEpisodes(items) {
|
|
4040
|
+
if (items.length === 0) {
|
|
4041
|
+
episodes.className = "empty";
|
|
4042
|
+
episodes.textContent = "\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u8BB0\u5FC6\u3002\u9ED8\u8BA4\u5728 10 \u5206\u949F\u7A97\u53E3\u9759\u9ED8 2 \u5206\u949F\u540E\u751F\u6210\uFF0C\u4E5F\u53EF\u4EE5\u8FD0\u884C chattercatcher process episodes \u624B\u52A8\u89E6\u53D1\u3002";
|
|
4043
|
+
return;
|
|
4044
|
+
}
|
|
4045
|
+
episodes.className = "";
|
|
4046
|
+
episodes.innerHTML = \`
|
|
4047
|
+
<div class="message-list">
|
|
4048
|
+
\${items.map((item) => \`
|
|
4049
|
+
<article class="message-item">
|
|
4050
|
+
<div class="message-meta">
|
|
4051
|
+
<span>\${escapeHtml(formatDateTime(item.startedAt))} - \${escapeHtml(formatDateTime(item.endedAt))}</span>
|
|
4052
|
+
<span>\${escapeHtml(displayChatName(item.chatName, "feishu"))}</span>
|
|
4053
|
+
<span>\${escapeHtml(item.messageCount)} \u6761\u6D88\u606F</span>
|
|
4054
|
+
</div>
|
|
4055
|
+
<div class="message-body">\${escapeHtml(item.summary)}</div>
|
|
4056
|
+
</article>
|
|
4057
|
+
\`).join("")}
|
|
4058
|
+
</div>
|
|
4059
|
+
\`;
|
|
4060
|
+
}
|
|
4061
|
+
|
|
3751
4062
|
function renderChats(items) {
|
|
3752
4063
|
if (items.length === 0) {
|
|
3753
4064
|
chats.className = "empty";
|
|
@@ -3823,15 +4134,17 @@ function buildHtml() {
|
|
|
3823
4134
|
}
|
|
3824
4135
|
|
|
3825
4136
|
async function load() {
|
|
3826
|
-
const [status, recent, chatList, fileList, jobList] = await Promise.all([
|
|
4137
|
+
const [status, recent, episodeList, chatList, fileList, jobList] = await Promise.all([
|
|
3827
4138
|
fetch("/api/status").then((response) => response.json()),
|
|
3828
4139
|
fetch("/api/messages/recent?limit=20").then((response) => response.json()),
|
|
4140
|
+
fetch("/api/episodes?limit=10").then((response) => response.json()),
|
|
3829
4141
|
fetch("/api/chats").then((response) => response.json()),
|
|
3830
4142
|
fetch("/api/files").then((response) => response.json()),
|
|
3831
4143
|
fetch("/api/file-jobs").then((response) => response.json()),
|
|
3832
4144
|
]);
|
|
3833
4145
|
renderMetrics(status);
|
|
3834
4146
|
renderMessages(recent.items);
|
|
4147
|
+
renderEpisodes(episodeList.items);
|
|
3835
4148
|
renderChats(chatList.items);
|
|
3836
4149
|
renderFiles(fileList.items);
|
|
3837
4150
|
renderFileJobs(jobList.items);
|
|
@@ -3880,6 +4193,7 @@ function createWebApp(config) {
|
|
|
3880
4193
|
const app = Fastify({ logger: false });
|
|
3881
4194
|
const database = openDatabase(config);
|
|
3882
4195
|
const messages = new MessageRepository(database);
|
|
4196
|
+
const episodes = new EpisodeRepository(database);
|
|
3883
4197
|
const fileJobs = new FileJobRepository(database);
|
|
3884
4198
|
app.addHook("onClose", async () => {
|
|
3885
4199
|
database.close();
|
|
@@ -3890,6 +4204,7 @@ function createWebApp(config) {
|
|
|
3890
4204
|
data: {
|
|
3891
4205
|
chats: messages.getChatCount(),
|
|
3892
4206
|
messages: messages.getMessageCount(),
|
|
4207
|
+
episodes: episodes.getEpisodeCount(),
|
|
3893
4208
|
files: messages.listFiles(1e3).length
|
|
3894
4209
|
},
|
|
3895
4210
|
rag: {
|
|
@@ -3925,6 +4240,12 @@ function createWebApp(config) {
|
|
|
3925
4240
|
items: messages.listRecentMessages(limit)
|
|
3926
4241
|
};
|
|
3927
4242
|
});
|
|
4243
|
+
app.get("/api/episodes", async (request) => {
|
|
4244
|
+
const limit = parseLimit(request.query.limit, 20, 100);
|
|
4245
|
+
return {
|
|
4246
|
+
items: episodes.listRecentEpisodes(limit)
|
|
4247
|
+
};
|
|
4248
|
+
});
|
|
3928
4249
|
app.post("/api/process/messages", async (_request, reply) => {
|
|
3929
4250
|
try {
|
|
3930
4251
|
return await processMessagesNow({
|