@lih-x-x/kmr 1.0.0 → 1.0.2
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/chunk-X36FOW5Y.js +47 -0
- package/dist/cli.js +51 -34
- package/dist/config-KM4HTJA2.js +12 -0
- package/dist/index.js +1134 -207
- package/package.json +11 -3
- package/dist/agent/claudeCode.d.ts +0 -12
- package/dist/agent/claudeCode.js +0 -109
- package/dist/agent/prompt.d.ts +0 -3
- package/dist/agent/prompt.js +0 -33
- package/dist/agent/types.d.ts +0 -7
- package/dist/agent/types.js +0 -1
- package/dist/cli-init.d.ts +0 -1
- package/dist/cli-init.js +0 -12
- package/dist/cli.d.ts +0 -2
- package/dist/config.d.ts +0 -17
- package/dist/config.js +0 -38
- package/dist/index.d.ts +0 -1
- package/dist/lark/client.d.ts +0 -8
- package/dist/lark/client.js +0 -68
- package/dist/lark/docReader.d.ts +0 -9
- package/dist/lark/docReader.js +0 -75
- package/dist/lark/messenger.d.ts +0 -22
- package/dist/lark/messenger.js +0 -156
- package/dist/lark/router.d.ts +0 -20
- package/dist/lark/router.js +0 -75
- package/dist/lark/taskCreator.d.ts +0 -15
- package/dist/lark/taskCreator.js +0 -41
- package/dist/query/finder.d.ts +0 -2
- package/dist/query/finder.js +0 -18
- package/dist/query/handler.d.ts +0 -8
- package/dist/query/handler.js +0 -17
- package/dist/session/manager.d.ts +0 -12
- package/dist/session/manager.js +0 -114
- package/dist/session/skill.d.ts +0 -1
- package/dist/session/skill.js +0 -19
- package/dist/storage/jsonStore.d.ts +0 -11
- package/dist/storage/jsonStore.js +0 -51
- package/dist/storage/types.d.ts +0 -52
- package/dist/storage/types.js +0 -1
- package/dist/web/openBrowser.d.ts +0 -1
- package/dist/web/openBrowser.js +0 -15
- package/dist/web/public/public/app.js +0 -344
- package/dist/web/public/public/index.html +0 -28
- package/dist/web/public/style.css +0 -428
- package/dist/web/server.d.ts +0 -6
- package/dist/web/server.js +0 -209
- /package/dist/{web → public}/public/app.js +0 -0
- /package/dist/{web → public}/public/index.html +0 -0
- /package/dist/{web/public → public}/public/style.css +0 -0
package/dist/index.js
CHANGED
|
@@ -1,214 +1,1141 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import {
|
|
2
|
+
KMR_DIR,
|
|
3
|
+
loadConfig
|
|
4
|
+
} from "./chunk-X36FOW5Y.js";
|
|
5
|
+
|
|
6
|
+
// src/lark/client.ts
|
|
7
|
+
import * as lark from "@larksuiteoapi/node-sdk";
|
|
8
|
+
function createLarkClient(config) {
|
|
9
|
+
return new lark.Client({
|
|
10
|
+
appId: config.lark.appId,
|
|
11
|
+
appSecret: config.lark.appSecret,
|
|
12
|
+
appType: lark.AppType.SelfBuild
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function createEventDispatcher(onMessage) {
|
|
16
|
+
const dispatcher = new lark.EventDispatcher({});
|
|
17
|
+
const processedMessages = /* @__PURE__ */ new Set();
|
|
18
|
+
dispatcher.register({
|
|
19
|
+
"im.message.receive_v1": async (data) => {
|
|
20
|
+
console.log(`[recv] \u6536\u5230\u98DE\u4E66\u4E8B\u4EF6:`, JSON.stringify(data, null, 2));
|
|
21
|
+
const message = data.message;
|
|
22
|
+
if (message.message_type !== "text") {
|
|
23
|
+
console.log(`[recv] \u5FFD\u7565\u975E\u6587\u672C\u6D88\u606F, type=${message.message_type}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const messageId = message.message_id;
|
|
27
|
+
if (processedMessages.has(messageId)) {
|
|
28
|
+
console.log(`[dedup] \u8DF3\u8FC7\u91CD\u590D\u6D88\u606F: ${messageId}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
processedMessages.add(messageId);
|
|
32
|
+
if (processedMessages.size > 1e3) {
|
|
33
|
+
const first = processedMessages.values().next().value;
|
|
34
|
+
processedMessages.delete(first);
|
|
35
|
+
}
|
|
36
|
+
const content = JSON.parse(message.content);
|
|
37
|
+
let text = content.text || "";
|
|
38
|
+
const chatId = message.chat_id;
|
|
39
|
+
const isMentioned = message.mentions && message.mentions.length > 0;
|
|
40
|
+
const isGroup = message.chat_type === "group";
|
|
41
|
+
if (isMentioned) {
|
|
42
|
+
for (const mention of message.mentions) {
|
|
43
|
+
if (mention.key) {
|
|
44
|
+
text = text.replace(mention.key, "").trim();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (isGroup && !isMentioned) {
|
|
49
|
+
const hasDocLink = /(https?:\/\/[^\s]*feishu\.cn\/[^\s]+)/.test(text);
|
|
50
|
+
const isConfirmOrReject = /^(?:\/confirm|\/reject|确认创建|取消创建|不创建|不用创建|全部创建|跳过待办|跳过|算了)\b/.test(text.trim());
|
|
51
|
+
if (!hasDocLink && !isConfirmOrReject) {
|
|
52
|
+
console.log(`[recv] \u7FA4\u804A\u975E@\u6D88\u606F\u4E14\u975E\u6587\u6863\u94FE\u63A5/\u5F85\u529E\u786E\u8BA4, \u5FFD\u7565`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
console.log(`[recv] \u89E3\u6790\u6D88\u606F: messageId=${messageId}, chatId=${chatId}, chatType=${message.chat_type}, text="${text}"`);
|
|
57
|
+
const senderId = data.sender?.sender_id?.open_id || "unknown";
|
|
58
|
+
await onMessage(messageId, text, chatId, senderId, { isGroup, isMentioned });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return dispatcher;
|
|
62
|
+
}
|
|
63
|
+
function startWSClient(client, dispatcher) {
|
|
64
|
+
const wsClient = new lark.WSClient({
|
|
65
|
+
appId: client.appId,
|
|
66
|
+
appSecret: client.appSecret,
|
|
67
|
+
loggerLevel: lark.LoggerLevel.info
|
|
68
|
+
});
|
|
69
|
+
wsClient.start({ eventDispatcher: dispatcher });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/lark/docReader.ts
|
|
73
|
+
function extractDocumentId(url) {
|
|
74
|
+
const patterns = [
|
|
75
|
+
/feishu\.cn\/docx\/([a-zA-Z0-9]+)/,
|
|
76
|
+
/feishu\.cn\/wiki\/([a-zA-Z0-9]+)/,
|
|
77
|
+
/feishu\.cn\/docs\/([a-zA-Z0-9]+)/
|
|
78
|
+
];
|
|
79
|
+
for (const pattern of patterns) {
|
|
80
|
+
const match = url.match(pattern);
|
|
81
|
+
if (match) return match[1];
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
var DocReader = class {
|
|
86
|
+
constructor(client) {
|
|
87
|
+
this.client = client;
|
|
88
|
+
}
|
|
89
|
+
client;
|
|
90
|
+
async getDocumentTitle(documentId) {
|
|
91
|
+
try {
|
|
92
|
+
const response = await this.client.docx.document.get({
|
|
93
|
+
path: { document_id: documentId }
|
|
94
|
+
});
|
|
95
|
+
return response.data?.document?.title || "";
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error(`[docReader] \u83B7\u53D6\u6587\u6863\u6807\u9898\u5931\u8D25: ${err.message}`);
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async readDocument(documentId) {
|
|
102
|
+
const blocks = [];
|
|
103
|
+
let pageToken;
|
|
104
|
+
do {
|
|
105
|
+
const response = await this.client.docx.documentBlock.list({
|
|
106
|
+
path: { document_id: documentId },
|
|
107
|
+
params: { page_size: 500, ...pageToken ? { page_token: pageToken } : {} }
|
|
108
|
+
});
|
|
109
|
+
if (response.data?.items) {
|
|
110
|
+
for (const block of response.data.items) {
|
|
111
|
+
const text = this.extractBlockText(block);
|
|
112
|
+
if (text) blocks.push(text);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
pageToken = response.data?.page_token || void 0;
|
|
116
|
+
} while (pageToken);
|
|
117
|
+
console.log(`[docReader] \u6587\u6863 ${documentId} \u5171\u63D0\u53D6 ${blocks.length} \u4E2A\u6587\u672C\u5757`);
|
|
118
|
+
return blocks.join("\n");
|
|
119
|
+
}
|
|
120
|
+
extractBlockText(block) {
|
|
121
|
+
const textSources = [
|
|
122
|
+
block.text,
|
|
123
|
+
// 普通文本
|
|
124
|
+
block.heading,
|
|
125
|
+
// 标题
|
|
126
|
+
block.bullet,
|
|
127
|
+
// 无序列表
|
|
128
|
+
block.ordered,
|
|
129
|
+
// 有序列表
|
|
130
|
+
block.code,
|
|
131
|
+
// 代码块
|
|
132
|
+
block.quote,
|
|
133
|
+
// 引用
|
|
134
|
+
block.todo,
|
|
135
|
+
// 待办
|
|
136
|
+
block.callout
|
|
137
|
+
// 高亮块
|
|
138
|
+
];
|
|
139
|
+
for (const source of textSources) {
|
|
140
|
+
if (source?.elements) {
|
|
141
|
+
const text = source.elements.map((el) => el.text_run?.content || el.mention_user?.content || "").join("");
|
|
142
|
+
if (text) return text;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// src/lark/messenger.ts
|
|
150
|
+
var Messenger = class {
|
|
151
|
+
constructor(client) {
|
|
152
|
+
this.client = client;
|
|
153
|
+
}
|
|
154
|
+
client;
|
|
155
|
+
async replyText(messageId, text) {
|
|
156
|
+
await this.client.im.message.reply({
|
|
157
|
+
path: { message_id: messageId },
|
|
158
|
+
data: {
|
|
159
|
+
content: JSON.stringify({ text }),
|
|
160
|
+
msg_type: "text"
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async replyMeetingSummary(messageId, record) {
|
|
165
|
+
const lines = [
|
|
166
|
+
`\u2705 \u4F1A\u8BAE\u5173\u952E\u4FE1\u606F\u63D0\u53D6\u5B8C\u6210`,
|
|
167
|
+
"",
|
|
168
|
+
`\u{1F4CB} **${record.summary.title}**`,
|
|
169
|
+
`\u{1F4C5} \u65E5\u671F\uFF1A${record.summary.date}`,
|
|
170
|
+
`\u{1F465} \u53C2\u4E0E\u4EBA\uFF1A${record.summary.participants.join("\u3001")}`,
|
|
171
|
+
"",
|
|
172
|
+
"**\u6838\u5FC3\u8981\u70B9\uFF1A**",
|
|
173
|
+
...record.summary.keyPoints.map((p) => `\u2022 ${p}`)
|
|
174
|
+
];
|
|
175
|
+
if (record.todos.length > 0) {
|
|
176
|
+
lines.push("", "**\u5F85\u529E\u4E8B\u9879\uFF1A**");
|
|
177
|
+
for (const todo of record.todos) {
|
|
178
|
+
lines.push(`\u2022 ${todo.content}\uFF08${todo.owner}\uFF0C\u622A\u6B62 ${todo.deadline}\uFF09`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (record.risks.length > 0) {
|
|
182
|
+
lines.push("", "**\u98CE\u9669\u9879\uFF1A**");
|
|
183
|
+
for (const risk of record.risks) {
|
|
184
|
+
lines.push(`\u2022 [${risk.severity}] ${risk.description}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (record.commitments.length > 0) {
|
|
188
|
+
lines.push("", "\u{1F91D} **\u5173\u952E\u5171\u8BC6\u4E0E\u627F\u8BFA\uFF1A**");
|
|
189
|
+
for (const c of record.commitments) {
|
|
190
|
+
lines.push(`\u2022 ${c.content}`);
|
|
191
|
+
lines.push(` \u76F8\u5173\u65B9\uFF1A${c.participants.join("\u3001")}${c.deadline ? `\uFF0C\u622A\u6B62 ${c.deadline}` : ""}`);
|
|
192
|
+
lines.push(` \u{1F4A1} ${c.context}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (record.reusableInsights.length > 0) {
|
|
196
|
+
lines.push("", "\u{1F9E0} **\u53EF\u590D\u7528\u77E5\u8BC6\uFF1A**");
|
|
197
|
+
for (const r of record.reusableInsights) {
|
|
198
|
+
lines.push(`\u2022 [${r.category}] ${r.content}`);
|
|
199
|
+
lines.push(` \u9002\u7528\u573A\u666F\uFF1A${r.scenario}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
await this.replyText(messageId, lines.join("\n"));
|
|
203
|
+
}
|
|
204
|
+
async replyRecordList(messageId, records) {
|
|
205
|
+
if (records.length === 0) {
|
|
206
|
+
await this.replyText(messageId, "\u6682\u65E0\u4F1A\u8BAE\u8BB0\u5F55");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const lines = [`\u{1F4C2} \u5171 ${records.length} \u6761\u4F1A\u8BAE\u8BB0\u5F55\uFF1A`, ""];
|
|
210
|
+
for (const r of records) {
|
|
211
|
+
lines.push(`\u2022 ${r.id}`);
|
|
212
|
+
lines.push(` ${r.summary.title}\uFF08${r.summary.date}\uFF09`);
|
|
213
|
+
if (r.documentUrl) lines.push(` ${r.documentUrl}`);
|
|
214
|
+
lines.push("");
|
|
215
|
+
}
|
|
216
|
+
lines.push("\u4F7F\u7528 /show <id> \u67E5\u770B\u8BE6\u60C5\uFF0C/del <id> \u5220\u9664\u8BB0\u5F55");
|
|
217
|
+
await this.replyText(messageId, lines.join("\n"));
|
|
218
|
+
}
|
|
219
|
+
async replyRecordDetail(messageId, record) {
|
|
220
|
+
const lines = [
|
|
221
|
+
`\u{1F4CB} **${record.summary.title}**`,
|
|
222
|
+
`\u{1F4C5} \u65E5\u671F\uFF1A${record.summary.date}`,
|
|
223
|
+
`\u{1F465} \u53C2\u4E0E\u4EBA\uFF1A${record.summary.participants.join("\u3001")}`,
|
|
224
|
+
`\u{1F194} ${record.id}`
|
|
225
|
+
];
|
|
226
|
+
if (record.documentUrl) {
|
|
227
|
+
lines.push(`\u{1F517} ${record.documentUrl}`);
|
|
228
|
+
}
|
|
229
|
+
lines.push("", "**\u6838\u5FC3\u8981\u70B9\uFF1A**");
|
|
230
|
+
for (const p of record.summary.keyPoints) {
|
|
231
|
+
lines.push(`\u2022 ${p}`);
|
|
232
|
+
}
|
|
233
|
+
if (record.commitments && record.commitments.length > 0) {
|
|
234
|
+
lines.push("", "\u{1F91D} **\u5173\u952E\u5171\u8BC6\u4E0E\u627F\u8BFA\uFF1A**");
|
|
235
|
+
for (const c of record.commitments) {
|
|
236
|
+
lines.push(`\u2022 ${c.content}`);
|
|
237
|
+
lines.push(` \u76F8\u5173\u65B9\uFF1A${c.participants.join("\u3001")}${c.deadline ? `\uFF0C\u622A\u6B62 ${c.deadline}` : ""}`);
|
|
238
|
+
lines.push(` \u{1F4A1} ${c.context}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (record.reusableInsights && record.reusableInsights.length > 0) {
|
|
242
|
+
lines.push("", "\u{1F9E0} **\u53EF\u590D\u7528\u77E5\u8BC6\uFF1A**");
|
|
243
|
+
for (const r of record.reusableInsights) {
|
|
244
|
+
lines.push(`\u2022 [${r.category}] ${r.content}`);
|
|
245
|
+
lines.push(` \u9002\u7528\u573A\u666F\uFF1A${r.scenario}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (record.todos && record.todos.length > 0) {
|
|
249
|
+
lines.push("", "**\u5F85\u529E\u4E8B\u9879\uFF1A**");
|
|
250
|
+
for (const t of record.todos) {
|
|
251
|
+
lines.push(`\u2022 ${t.content}\uFF08${t.owner}\uFF0C\u622A\u6B62 ${t.deadline}\uFF09`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (record.risks && record.risks.length > 0) {
|
|
255
|
+
lines.push("", "**\u98CE\u9669\u9879\uFF1A**");
|
|
256
|
+
for (const r of record.risks) {
|
|
257
|
+
lines.push(`\u2022 [${r.severity}] ${r.description}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
await this.replyText(messageId, lines.join("\n"));
|
|
261
|
+
}
|
|
262
|
+
async replySearchResults(messageId, results) {
|
|
263
|
+
if (results.length === 0) {
|
|
264
|
+
await this.replyText(messageId, "\u672A\u627E\u5230\u76F8\u5173\u4F1A\u8BAE\u8BB0\u5F55");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const lines = ["\u{1F50D} \u627E\u5230\u4EE5\u4E0B\u76F8\u5173\u4F1A\u8BAE\uFF1A", ""];
|
|
268
|
+
for (const r of results.slice(0, 3)) {
|
|
269
|
+
lines.push(`\u2022 **${r.summary.title}**\uFF08${r.summary.date}\uFF09`);
|
|
270
|
+
lines.push(` \u8981\u70B9\uFF1A${r.summary.keyPoints[0] || "\u65E0"}`);
|
|
271
|
+
lines.push(` \u94FE\u63A5\uFF1A${r.documentUrl}`);
|
|
272
|
+
lines.push("");
|
|
273
|
+
}
|
|
274
|
+
await this.replyText(messageId, lines.join("\n"));
|
|
275
|
+
}
|
|
276
|
+
async replyTodoConfirmation(messageId, todos) {
|
|
277
|
+
const lines = [
|
|
278
|
+
`\u{1F4DD} \u68C0\u6D4B\u5230 ${todos.length} \u6761\u5F85\u529E\uFF0C\u662F\u5426\u521B\u5EFA\u98DE\u4E66\u4EFB\u52A1\uFF1F`,
|
|
279
|
+
""
|
|
280
|
+
];
|
|
281
|
+
todos.forEach((t, i) => {
|
|
282
|
+
lines.push(`${i + 1}. [${t.owner}] ${t.content}${t.deadline ? `\uFF08\u622A\u6B62 ${t.deadline}\uFF09` : ""}`);
|
|
283
|
+
});
|
|
284
|
+
lines.push("", "\u56DE\u590D /confirm all \u521B\u5EFA\u5168\u90E8");
|
|
285
|
+
lines.push("\u56DE\u590D /confirm 1,2 \u521B\u5EFA\u9009\u4E2D\u7684\u4EFB\u52A1");
|
|
286
|
+
lines.push("\u56DE\u590D /reject \u53D6\u6D88\u521B\u5EFA");
|
|
287
|
+
lines.push("", "\u23F1 1 \u5206\u949F\u5185\u672A\u56DE\u590D\u5C06\u81EA\u52A8\u53D6\u6D88");
|
|
288
|
+
await this.replyText(messageId, lines.join("\n"));
|
|
289
|
+
}
|
|
290
|
+
async replyTaskResults(messageId, results) {
|
|
291
|
+
const lines = ["\u{1F4CB} \u98DE\u4E66\u4EFB\u52A1\u521B\u5EFA\u7ED3\u679C\uFF1A", ""];
|
|
292
|
+
for (const r of results) {
|
|
293
|
+
if (r.success) {
|
|
294
|
+
lines.push(`\u2705 ${r.summary}`);
|
|
295
|
+
if (r.url) lines.push(` ${r.url}`);
|
|
296
|
+
} else {
|
|
297
|
+
lines.push(`\u274C ${r.summary} \u2014 ${r.error || "\u521B\u5EFA\u5931\u8D25"}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
await this.replyText(messageId, lines.join("\n"));
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// src/lark/router.ts
|
|
305
|
+
function parseMessage(text) {
|
|
306
|
+
const trimmed = text.trim();
|
|
307
|
+
if (/^(?:\/reject|取消创建|不创建|不用创建|跳过待办|跳过|算了)$/i.test(trimmed)) {
|
|
308
|
+
return { type: "reject_tasks" /* REJECT_TASKS */, raw: text };
|
|
309
|
+
}
|
|
310
|
+
const confirmAllMatch = /^(?:\/confirm\s+all|确认创建全部|全部创建)$/i.test(trimmed);
|
|
311
|
+
if (confirmAllMatch) {
|
|
312
|
+
return { type: "confirm_tasks" /* CONFIRM_TASKS */, confirmIds: "all", raw: text };
|
|
313
|
+
}
|
|
314
|
+
const confirmMatch = trimmed.match(/^(?:\/confirm|确认创建)\s+([\d,\s]+)/);
|
|
315
|
+
if (confirmMatch) {
|
|
316
|
+
const ids = confirmMatch[1].split(/[,\s,]+/).map(Number).filter((n) => n > 0);
|
|
317
|
+
if (ids.length > 0) {
|
|
318
|
+
return { type: "confirm_tasks" /* CONFIRM_TASKS */, confirmIds: ids, raw: text };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const delMatch = trimmed.match(/^(?:\/del|删掉|删除)\s+(.+)/);
|
|
322
|
+
if (delMatch) {
|
|
323
|
+
return {
|
|
324
|
+
type: "delete_record" /* DELETE_RECORD */,
|
|
325
|
+
deleteId: delMatch[1].trim(),
|
|
326
|
+
raw: text
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const showMatch = trimmed.match(/^(?:\/show)\s+(.+)/) || trimmed.match(/^(?:展示|列出|显示|查看)\s*(meeting_\w+)/) || trimmed.match(/^(?:展示|列出|显示|查看)\s*(.+?)的?详情/);
|
|
330
|
+
if (showMatch) {
|
|
331
|
+
return {
|
|
332
|
+
type: "show_detail" /* SHOW_DETAIL */,
|
|
333
|
+
showId: showMatch[1].trim(),
|
|
334
|
+
raw: text
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (/^\/listall$/i.test(trimmed) || /^(展示|列出|显示|查看)(所有|全部|历史)(记录|会议)/.test(trimmed)) {
|
|
338
|
+
return { type: "list_all" /* LIST_ALL */, raw: text };
|
|
339
|
+
}
|
|
340
|
+
const findMatch = text.match(/^\/find\s+(.+)/);
|
|
341
|
+
if (findMatch) {
|
|
342
|
+
return {
|
|
343
|
+
type: "find_query" /* FIND_QUERY */,
|
|
344
|
+
query: findMatch[1].trim(),
|
|
345
|
+
raw: text
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
const urlMatch = text.match(/(https?:\/\/[^\s]*feishu\.cn\/[^\s]+)/);
|
|
349
|
+
if (urlMatch) {
|
|
350
|
+
const docId = extractDocumentId(urlMatch[1]);
|
|
351
|
+
if (docId) {
|
|
352
|
+
return {
|
|
353
|
+
type: "document_link" /* DOCUMENT_LINK */,
|
|
354
|
+
documentId: docId,
|
|
355
|
+
raw: text
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return { type: "unknown" /* UNKNOWN */, raw: text };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/agent/claudeCode.ts
|
|
363
|
+
import { execFile } from "child_process";
|
|
364
|
+
import { promisify } from "util";
|
|
365
|
+
|
|
366
|
+
// src/agent/prompt.ts
|
|
367
|
+
var EXTRACT_PROMPT = `\u4F60\u662F\u4E00\u4E2A\u9AD8\u7EA7\u4F1A\u8BAE\u5206\u6790\u5E08\uFF0C\u4E0D\u662F\u8BB0\u5F55\u5458\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u4ECE\u4F1A\u8BAE\u7EAA\u8981\u4E2D\u63D0\u70BC\u771F\u6B63\u6709\u4EF7\u503C\u7684\u4FE1\u606F\uFF0C\u800C\u4E0D\u662F\u6D41\u6C34\u8D26\u5F0F\u590D\u8FF0\u3002
|
|
368
|
+
|
|
369
|
+
\u4F60\u9700\u8981\u5E26\u7740\u4E24\u4E2A\u6838\u5FC3\u76EE\u6807\u5206\u6790\u4F1A\u8BAE\u5185\u5BB9\uFF1A
|
|
370
|
+
1. **\u6EAF\u6E90\u8FFD\u8D23**\uFF1A\u63D0\u53D6\u6240\u6709\u660E\u786E\u7684\u627F\u8BFA\u3001\u7EA6\u5B9A\u3001\u51B3\u8BAE\u2014\u2014\u8C01\u7B54\u5E94\u4E86\u4EC0\u4E48\u3001\u4EC0\u4E48\u65F6\u5019\u5B8C\u6210\u3001\u8FBE\u6210\u4E86\u4EC0\u4E48\u5171\u8BC6\u3002\u5177\u4F53\u7684\u4EBA\u548C\u5177\u4F53\u7684\u65F6\u95F4\u662F\u6838\u5FC3\u951A\u70B9\uFF0C\u8FD9\u4E9B\u4FE1\u606F\u7528\u4E8E\u4E8B\u540E\u5FEB\u901F\u6EAF\u6E90\u3002
|
|
371
|
+
2. **\u77E5\u8BC6\u590D\u7528**\uFF1A\u63D0\u53D6\u4F1A\u8BAE\u4E2D\u63D0\u5230\u7684\u5DE5\u5177\u7528\u6CD5\u3001\u65B9\u6CD5\u8BBA\u3001\u7ECF\u9A8C\u6559\u8BAD\u3001\u6700\u4F73\u5B9E\u8DF5\u2014\u2014\u8FD9\u4E9B\u53EF\u4EE5\u63D0\u5347\u5DE5\u4F5C\u6548\u7387\u7684\u53EF\u590D\u7528\u77E5\u8BC6\u3002
|
|
372
|
+
|
|
373
|
+
\u5206\u6790\u8981\u6C42\uFF1A
|
|
374
|
+
- participants\uFF1A\u5FC5\u987B\u5217\u51FA\u7EAA\u8981\u6587\u6863\u4E2D\u8BB0\u5F55\u7684\u53C2\u4E0E\u8BA8\u8BBA\u548C\u51B3\u7B56\u6216\u8005\u88AB\u63D0\u53CA\u7684\u6838\u5FC3\u6210\u5458\u540D\u5B57
|
|
375
|
+
- keyPoints\uFF1A\u53EA\u4FDD\u7559\u6700\u5173\u952E\u7684 3-8 \u4E2A\u51B3\u7B56\u6027\u8981\u70B9\uFF0C\u7F57\u5217\u7B80\u8981\u8BA8\u8BBA\u8FC7\u7A0B
|
|
376
|
+
- todos\uFF1A\u53EA\u63D0\u53D6\u6709\u660E\u786E\u8D1F\u8D23\u4EBA\u548C\u53EF\u6267\u884C\u5185\u5BB9\u7684\u5F85\u529E\uFF0C\u4E0D\u8981\u6A21\u7CCA\u7684"\u540E\u7EED\u8DDF\u8FDB"
|
|
377
|
+
- risks\uFF1A\u53EA\u63D0\u53D6\u771F\u6B63\u7684\u98CE\u9669\u548C\u672A\u51B3\u4E8B\u9879\uFF0C\u8981\u6709\u5177\u4F53\u7684\u7F13\u89E3\u65B9\u6848
|
|
378
|
+
- commitments\u3010\u6700\u91CD\u8981\u3011\uFF1A\u63D0\u53D6\u6240\u6709\u627F\u8BFA\u3001\u7EA6\u5B9A\u3001\u51B3\u8BAE\u3002context \u5B57\u6BB5\u5FC5\u987B\u5199\u4F60\u7684\u5206\u6790\u2014\u2014\u4E3A\u4EC0\u4E48\u4F1A\u8FBE\u6210\u8FD9\u4E2A\u5171\u8BC6\uFF0C\u80CC\u666F\u662F\u4EC0\u4E48\uFF0C\u8C01\u8FBE\u6210\u7684\uFF0C\u4E0D\u80FD\u53EA\u642C\u8FD0\u539F\u6587
|
|
379
|
+
- reusableInsights\u3010\u6700\u91CD\u8981\u3011\uFF1A\u63D0\u53D6\u53EF\u590D\u7528\u7684\u77E5\u8BC6\u3002scenario \u5B57\u6BB5\u5FC5\u987B\u5199\u8FD9\u4E2A\u77E5\u8BC6\u5728\u4EC0\u4E48\u573A\u666F\u4E0B\u6709\u7528\uFF0C\u4E0D\u80FD\u6CDB\u6CDB\u800C\u8C08\uFF0C\u8981\u6709\u5177\u4F53\u64CD\u4F5C\u5B9E\u8DF5\u6B65\u9AA4\u6307\u5BFC
|
|
380
|
+
|
|
381
|
+
\u3010\u91CD\u8981\u3011\u76F4\u63A5\u8F93\u51FA JSON\uFF0C\u4E0D\u8981\u8F93\u51FA\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3001\u89E3\u91CA\u6216 markdown \u4EE3\u7801\u5757\u3002\u4EC5\u8F93\u51FA\u4EE5\u4E0B\u683C\u5F0F\u7684 JSON\uFF1A
|
|
382
|
+
{"summary":{"title":"\u4F1A\u8BAE\u6807\u9898","date":"YYYY-MM-DD","participants":["\u53C2\u4E0E\u4EBA1"],"keyPoints":["\u51B3\u7B56\u6027\u8981\u70B9\uFF0C\u4E0D\u662F\u6D41\u6C34\u8D26"]},"todos":[{"content":"\u5177\u4F53\u53EF\u6267\u884C\u5185\u5BB9","owner":"\u8D1F\u8D23\u4EBA","deadline":"YYYY-MM-DD","status":"pending"}],"risks":[{"description":"\u5177\u4F53\u98CE\u9669","severity":"high|medium|low","mitigation":"\u5177\u4F53\u7F13\u89E3\u65B9\u6848"}],"projectRelations":[{"project":"\u9879\u76EE\u540D","relation":"\u5173\u8054\u63CF\u8FF0"}],"commitments":[{"content":"\u627F\u8BFA/\u7EA6\u5B9A/\u51B3\u8BAE\u7684\u5177\u4F53\u5185\u5BB9","participants":["\u76F8\u5173\u65B91","\u76F8\u5173\u65B92"],"deadline":"YYYY-MM-DD","context":"\u4F60\u7684\u5206\u6790\uFF1A\u4E3A\u4EC0\u4E48\u8FBE\u6210\u8FD9\u4E2A\u5171\u8BC6\uFF0C\u80CC\u666F\u548C\u5F71\u54CD\u662F\u4EC0\u4E48"}],"reusableInsights":[{"category":"tool|methodology|lesson|best-practice","content":"\u77E5\u8BC6\u70B9\u7684\u5177\u4F53\u5185\u5BB9","scenario":"\u5728\u4EC0\u4E48\u573A\u666F\u4E0B\u53EF\u4EE5\u590D\u7528\u8FD9\u4E2A\u77E5\u8BC6","source":"\u8C01\u63D0\u51FA\u7684"}]}
|
|
383
|
+
|
|
384
|
+
\u4F1A\u8BAE\u7EAA\u8981\u5185\u5BB9\uFF1A
|
|
385
|
+
`;
|
|
386
|
+
var SEARCH_KEYWORDS_PROMPT = `\u4F60\u662F\u4E00\u4E2A\u641C\u7D22\u52A9\u624B\u3002\u7528\u6237\u60F3\u67E5\u627E\u5386\u53F2\u4F1A\u8BAE\u8BB0\u5F55\uFF0C\u8BF7\u5C06\u7528\u6237\u7684\u81EA\u7136\u8BED\u8A00\u67E5\u8BE2\u8F6C\u6362\u4E3A\u641C\u7D22\u5173\u952E\u8BCD\u5217\u8868\u3002
|
|
387
|
+
|
|
388
|
+
\u8981\u6C42\uFF1A
|
|
389
|
+
- \u8FD4\u56DE 3-5 \u4E2A\u6700\u76F8\u5173\u7684\u5173\u952E\u8BCD
|
|
390
|
+
- \u4EC5\u8FD4\u56DE JSON \u6570\u7EC4\u683C\u5F0F\uFF0C\u4E0D\u8981\u5176\u4ED6\u5185\u5BB9
|
|
391
|
+
- \u793A\u4F8B\uFF1A["\u5173\u952E\u8BCD1", "\u5173\u952E\u8BCD2", "\u5173\u952E\u8BCD3"]
|
|
392
|
+
|
|
393
|
+
\u7528\u6237\u67E5\u8BE2\uFF1A`;
|
|
394
|
+
var RANK_RESULTS_PROMPT = `\u4F60\u662F\u4E00\u4E2A\u641C\u7D22\u6392\u5E8F\u52A9\u624B\u3002\u7528\u6237\u67E5\u8BE2\u548C\u5019\u9009\u4F1A\u8BAE\u8BB0\u5F55\u5982\u4E0B\uFF0C\u8BF7\u6309\u76F8\u5173\u6027\u4ECE\u9AD8\u5230\u4F4E\u6392\u5E8F\u3002
|
|
395
|
+
|
|
396
|
+
\u4EC5\u8FD4\u56DE\u6392\u5E8F\u540E\u7684\u4F1A\u8BAE ID JSON \u6570\u7EC4\uFF0C\u4E0D\u8981\u5176\u4ED6\u5185\u5BB9\u3002
|
|
397
|
+
\u793A\u4F8B\uFF1A["meeting_003", "meeting_001"]
|
|
398
|
+
|
|
399
|
+
\u7528\u6237\u67E5\u8BE2\uFF1A`;
|
|
400
|
+
|
|
401
|
+
// src/agent/claudeCode.ts
|
|
402
|
+
var execFileAsync = promisify(execFile);
|
|
403
|
+
var ClaudeCodeProvider = class {
|
|
404
|
+
constructor(timeout = 12e4) {
|
|
405
|
+
this.timeout = timeout;
|
|
406
|
+
}
|
|
407
|
+
timeout;
|
|
408
|
+
name = "claude-code";
|
|
409
|
+
async extract(content) {
|
|
410
|
+
const prompt = EXTRACT_PROMPT + content;
|
|
411
|
+
console.log(`[ClaudeCodeProvider] \u63D0\u53D6\u4FE1\u606F\u7684 prompt:
|
|
412
|
+
${prompt}
|
|
413
|
+
--- End of Prompt ---`);
|
|
414
|
+
const output = await this.callAcpx(prompt);
|
|
415
|
+
const parsed = JSON.parse(output);
|
|
416
|
+
return {
|
|
417
|
+
id: `meeting_${Date.now()}`,
|
|
418
|
+
documentUrl: "",
|
|
419
|
+
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
420
|
+
summary: parsed.summary,
|
|
421
|
+
todos: parsed.todos || [],
|
|
422
|
+
risks: parsed.risks || [],
|
|
423
|
+
projectRelations: parsed.projectRelations || [],
|
|
424
|
+
commitments: parsed.commitments || [],
|
|
425
|
+
reusableInsights: parsed.reusableInsights || [],
|
|
426
|
+
rawContent: content
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
async searchKeywords(query) {
|
|
430
|
+
const prompt = SEARCH_KEYWORDS_PROMPT + query;
|
|
431
|
+
const output = await this.callAcpx(prompt);
|
|
432
|
+
return JSON.parse(output);
|
|
433
|
+
}
|
|
434
|
+
async rankResults(query, candidates) {
|
|
435
|
+
if (candidates.length <= 1) return candidates;
|
|
436
|
+
const summaries = candidates.map((c) => ({
|
|
437
|
+
id: c.id,
|
|
438
|
+
title: c.summary.title,
|
|
439
|
+
keyPoints: c.summary.keyPoints
|
|
440
|
+
}));
|
|
441
|
+
const prompt = RANK_RESULTS_PROMPT + query + "\n\n\u5019\u9009\u4F1A\u8BAE\uFF1A\n" + JSON.stringify(summaries, null, 2);
|
|
442
|
+
const output = await this.callAcpx(prompt);
|
|
443
|
+
const rankedIds = JSON.parse(output);
|
|
444
|
+
const indexed = new Map(candidates.map((c) => [c.id, c]));
|
|
445
|
+
return rankedIds.map((id) => indexed.get(id)).filter(Boolean);
|
|
446
|
+
}
|
|
447
|
+
async callAcpx(prompt) {
|
|
448
|
+
try {
|
|
449
|
+
const { stdout } = await execFileAsync("acpx", ["--allowed-tools", "", "claude", "exec", prompt], {
|
|
450
|
+
timeout: this.timeout,
|
|
451
|
+
maxBuffer: 1024 * 1024
|
|
452
|
+
});
|
|
453
|
+
return this.extractJson(stdout);
|
|
454
|
+
} catch (err) {
|
|
455
|
+
if (err.killed) {
|
|
456
|
+
throw new Error(`acpx \u8C03\u7528\u8D85\u65F6 (${this.timeout}ms)`);
|
|
457
|
+
}
|
|
458
|
+
throw new Error(`acpx \u8C03\u7528\u5931\u8D25: ${err.message}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
extractJson(output) {
|
|
462
|
+
const lines = output.split("\n");
|
|
463
|
+
let jsonStart = -1;
|
|
464
|
+
let jsonEnd = -1;
|
|
465
|
+
for (let i = 0; i < lines.length; i++) {
|
|
466
|
+
const trimmed = lines[i].trim();
|
|
467
|
+
if (jsonStart === -1 && (trimmed.startsWith("{") || trimmed.startsWith("["))) {
|
|
468
|
+
jsonStart = i;
|
|
469
|
+
}
|
|
470
|
+
if (trimmed.endsWith("}") || trimmed.endsWith("]")) {
|
|
471
|
+
jsonEnd = i;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (jsonStart !== -1 && jsonEnd >= jsonStart) {
|
|
475
|
+
const candidate = lines.slice(jsonStart, jsonEnd + 1).join("\n").trim();
|
|
476
|
+
try {
|
|
477
|
+
JSON.parse(candidate);
|
|
478
|
+
return candidate;
|
|
479
|
+
} catch {
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const filtered = lines.filter((line) => {
|
|
483
|
+
const t = line.trim();
|
|
484
|
+
return t.length > 0 && !t.startsWith("[client]") && !t.startsWith("[tool]") && !t.startsWith("[thinking]") && !t.startsWith("[done]") && !t.startsWith("[error]") && !t.startsWith("[warn]") && !t.startsWith("[info]");
|
|
485
|
+
}).join("\n").trim();
|
|
486
|
+
if (filtered.length > 0) return filtered;
|
|
487
|
+
return output.trim();
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/storage/jsonStore.ts
|
|
492
|
+
import fs from "fs";
|
|
493
|
+
import path from "path";
|
|
494
|
+
var JsonStore = class {
|
|
495
|
+
constructor(dataDir) {
|
|
496
|
+
this.dataDir = dataDir;
|
|
497
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
498
|
+
}
|
|
499
|
+
dataDir;
|
|
500
|
+
async save(record) {
|
|
501
|
+
const date = record.summary.date || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
502
|
+
const fileName = `${date}_${record.id}.json`;
|
|
503
|
+
const filePath = path.join(this.dataDir, fileName);
|
|
504
|
+
fs.writeFileSync(filePath, JSON.stringify(record, null, 2), "utf-8");
|
|
505
|
+
return filePath;
|
|
506
|
+
}
|
|
507
|
+
async load(id) {
|
|
508
|
+
const files = this.getFiles();
|
|
509
|
+
const target = files.find((f) => f.includes(id));
|
|
510
|
+
if (!target) return null;
|
|
511
|
+
const raw = fs.readFileSync(target, "utf-8");
|
|
512
|
+
return JSON.parse(raw);
|
|
513
|
+
}
|
|
514
|
+
async list() {
|
|
515
|
+
const files = this.getFiles();
|
|
516
|
+
return files.map((f) => JSON.parse(fs.readFileSync(f, "utf-8")));
|
|
517
|
+
}
|
|
518
|
+
async search(keywords) {
|
|
519
|
+
const all = await this.list();
|
|
520
|
+
return all.filter((record) => {
|
|
521
|
+
const text = record.rawContent + " " + record.summary.title + " " + record.summary.keyPoints.join(" ");
|
|
522
|
+
return keywords.some((kw) => text.includes(kw));
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
async delete(id) {
|
|
526
|
+
const files = this.getFiles();
|
|
527
|
+
const target = files.find((f) => f.includes(id));
|
|
528
|
+
if (!target) return false;
|
|
529
|
+
fs.unlinkSync(target);
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
getFiles() {
|
|
533
|
+
if (!fs.existsSync(this.dataDir)) return [];
|
|
534
|
+
return fs.readdirSync(this.dataDir).filter((f) => f.endsWith(".json")).map((f) => path.join(this.dataDir, f));
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// src/query/finder.ts
|
|
539
|
+
import fs2 from "fs";
|
|
540
|
+
import path2 from "path";
|
|
541
|
+
async function grepMeetings(dataDir, keywords) {
|
|
542
|
+
if (!fs2.existsSync(dataDir)) return [];
|
|
543
|
+
const files = fs2.readdirSync(dataDir).filter((f) => f.endsWith(".json"));
|
|
544
|
+
const matches = [];
|
|
545
|
+
for (const file of files) {
|
|
546
|
+
const filePath = path2.join(dataDir, file);
|
|
547
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
548
|
+
const hasMatch = keywords.some((kw) => raw.includes(kw));
|
|
549
|
+
if (hasMatch) {
|
|
550
|
+
matches.push(JSON.parse(raw));
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return matches;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/query/handler.ts
|
|
557
|
+
var QueryHandler = class {
|
|
558
|
+
constructor(agent, dataDir) {
|
|
559
|
+
this.agent = agent;
|
|
560
|
+
this.dataDir = dataDir;
|
|
561
|
+
}
|
|
562
|
+
agent;
|
|
563
|
+
dataDir;
|
|
564
|
+
async find(query) {
|
|
565
|
+
const keywords = await this.agent.searchKeywords(query);
|
|
566
|
+
const candidates = await grepMeetings(this.dataDir, keywords);
|
|
567
|
+
if (candidates.length === 0) return [];
|
|
568
|
+
const ranked = await this.agent.rankResults(query, candidates);
|
|
569
|
+
return ranked.slice(0, 3);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// src/web/server.ts
|
|
574
|
+
import http from "http";
|
|
575
|
+
import fs3 from "fs";
|
|
576
|
+
import path3 from "path";
|
|
577
|
+
import { fileURLToPath } from "url";
|
|
578
|
+
var __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
579
|
+
var PUBLIC_DIR = path3.join(__dirname, "public");
|
|
580
|
+
var MIME_TYPES = {
|
|
581
|
+
".html": "text/html; charset=utf-8",
|
|
582
|
+
".css": "text/css; charset=utf-8",
|
|
583
|
+
".js": "application/javascript; charset=utf-8",
|
|
584
|
+
".json": "application/json; charset=utf-8",
|
|
585
|
+
".png": "image/png",
|
|
586
|
+
".svg": "image/svg+xml"
|
|
587
|
+
};
|
|
588
|
+
function serveStatic(res, urlPath) {
|
|
589
|
+
const safePath = path3.normalize(urlPath).replace(/^(\.\.[/\\])+/, "");
|
|
590
|
+
let filePath = path3.join(PUBLIC_DIR, safePath === "/" ? "index.html" : safePath);
|
|
591
|
+
if (!fs3.existsSync(filePath) || fs3.statSync(filePath).isDirectory()) {
|
|
592
|
+
filePath = path3.join(PUBLIC_DIR, "index.html");
|
|
593
|
+
}
|
|
594
|
+
const ext = path3.extname(filePath);
|
|
595
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
596
|
+
const content = fs3.readFileSync(filePath);
|
|
597
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
598
|
+
res.end(content);
|
|
599
|
+
}
|
|
600
|
+
function listSessions(kmrDir) {
|
|
601
|
+
const sessionsDir = path3.join(kmrDir, "sessions");
|
|
602
|
+
if (!fs3.existsSync(sessionsDir)) return [];
|
|
603
|
+
return fs3.readdirSync(sessionsDir).filter((d) => {
|
|
604
|
+
const full = path3.join(sessionsDir, d);
|
|
605
|
+
return fs3.statSync(full).isDirectory();
|
|
606
|
+
}).map((userId) => {
|
|
607
|
+
const summaryPath = path3.join(sessionsDir, userId, "summary.md");
|
|
608
|
+
let summaryPreview = "";
|
|
609
|
+
let messageCount = 0;
|
|
610
|
+
let lastActivity = "";
|
|
611
|
+
if (fs3.existsSync(summaryPath)) {
|
|
612
|
+
const content = fs3.readFileSync(summaryPath, "utf-8");
|
|
613
|
+
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
614
|
+
messageCount = lines.length;
|
|
615
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
616
|
+
if (lines[i].includes("] \u7528\u6237:")) {
|
|
617
|
+
summaryPreview = lines[i].replace(/^\[.*?\]\s*用户:\s*/, "").slice(0, 80);
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const lastLine = lines[lines.length - 1] || "";
|
|
622
|
+
const timeMatch = lastLine.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/);
|
|
623
|
+
if (timeMatch) lastActivity = timeMatch[1];
|
|
624
|
+
const stat = fs3.statSync(summaryPath);
|
|
625
|
+
if (!lastActivity) lastActivity = stat.mtime.toISOString().replace("T", " ").slice(0, 19);
|
|
626
|
+
} else {
|
|
627
|
+
const userDir = path3.join(sessionsDir, userId);
|
|
628
|
+
const stat = fs3.statSync(userDir);
|
|
629
|
+
lastActivity = stat.mtime.toISOString().replace("T", " ").slice(0, 19);
|
|
630
|
+
}
|
|
631
|
+
return { userId, summaryPreview, messageCount, lastActivity };
|
|
632
|
+
}).sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
|
|
633
|
+
}
|
|
634
|
+
function deleteSession(kmrDir, userId) {
|
|
635
|
+
const sessionDir = path3.join(kmrDir, "sessions", userId);
|
|
636
|
+
if (!fs3.existsSync(sessionDir)) return false;
|
|
637
|
+
fs3.rmSync(sessionDir, { recursive: true, force: true });
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
function getSessionDetail(kmrDir, userId) {
|
|
641
|
+
const summaryPath = path3.join(kmrDir, "sessions", userId, "summary.md");
|
|
642
|
+
if (!fs3.existsSync(summaryPath)) return null;
|
|
643
|
+
return fs3.readFileSync(summaryPath, "utf-8");
|
|
644
|
+
}
|
|
645
|
+
function startWebServer(store, kmrDir, port = 3e3) {
|
|
646
|
+
return new Promise((resolve, reject) => {
|
|
647
|
+
const server = http.createServer((req, res) => {
|
|
648
|
+
const url = new URL(req.url || "/", `http://localhost`);
|
|
649
|
+
if (url.pathname.startsWith("/api/")) {
|
|
650
|
+
handleApi(req, res, url, store, kmrDir);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
serveStatic(res, url.pathname);
|
|
654
|
+
});
|
|
655
|
+
server.on("error", (err) => {
|
|
656
|
+
if (err.code === "EADDRINUSE" && port < 3010) {
|
|
657
|
+
startWebServer(store, kmrDir, port + 1).then(resolve, reject);
|
|
658
|
+
} else {
|
|
659
|
+
reject(err);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
server.listen(port, () => {
|
|
663
|
+
const addr = server.address();
|
|
664
|
+
const actualPort = typeof addr === "object" && addr ? addr.port : port;
|
|
665
|
+
resolve({ server, port: actualPort });
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
function readBody(req) {
|
|
670
|
+
return new Promise((resolve, reject) => {
|
|
671
|
+
let body = "";
|
|
672
|
+
req.on("data", (chunk) => body += chunk);
|
|
673
|
+
req.on("end", () => resolve(body));
|
|
674
|
+
req.on("error", reject);
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
function jsonResponse(res, status, data) {
|
|
678
|
+
res.writeHead(status, {
|
|
679
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
680
|
+
"Access-Control-Allow-Origin": "*"
|
|
681
|
+
});
|
|
682
|
+
res.end(JSON.stringify(data));
|
|
683
|
+
}
|
|
684
|
+
async function handleApi(req, res, url, store, kmrDir) {
|
|
685
|
+
const method = req.method || "GET";
|
|
686
|
+
const pathname = url.pathname;
|
|
687
|
+
try {
|
|
688
|
+
if (pathname === "/api/meetings" && method === "GET") {
|
|
689
|
+
const meetings = await store.list();
|
|
690
|
+
jsonResponse(res, 200, { ok: true, data: meetings });
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const meetingMatch = pathname.match(/^\/api\/meetings\/(.+)$/);
|
|
694
|
+
if (meetingMatch && method === "GET") {
|
|
695
|
+
const record = await store.load(meetingMatch[1]);
|
|
696
|
+
if (!record) {
|
|
697
|
+
jsonResponse(res, 404, { ok: false, error: "\u4F1A\u8BAE\u8BB0\u5F55\u4E0D\u5B58\u5728" });
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
jsonResponse(res, 200, { ok: true, data: record });
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (meetingMatch && method === "DELETE") {
|
|
704
|
+
const deleted = await store.delete(meetingMatch[1]);
|
|
705
|
+
if (!deleted) {
|
|
706
|
+
jsonResponse(res, 404, { ok: false, error: "\u4F1A\u8BAE\u8BB0\u5F55\u4E0D\u5B58\u5728" });
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
jsonResponse(res, 200, { ok: true, data: null });
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (pathname === "/api/config" && method === "GET") {
|
|
713
|
+
const configPath = path3.join(kmrDir, "config.json");
|
|
714
|
+
const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
|
|
715
|
+
jsonResponse(res, 200, { ok: true, data: config });
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (pathname === "/api/config" && method === "PUT") {
|
|
719
|
+
const body = await readBody(req);
|
|
720
|
+
let parsed;
|
|
721
|
+
try {
|
|
722
|
+
parsed = JSON.parse(body);
|
|
723
|
+
} catch {
|
|
724
|
+
jsonResponse(res, 400, { ok: false, error: "\u65E0\u6548\u7684 JSON \u683C\u5F0F" });
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const configPath = path3.join(kmrDir, "config.json");
|
|
728
|
+
fs3.writeFileSync(configPath, JSON.stringify(parsed, null, 2), "utf-8");
|
|
729
|
+
jsonResponse(res, 200, { ok: true, data: parsed });
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (pathname === "/api/sessions" && method === "GET") {
|
|
733
|
+
const sessions = listSessions(kmrDir);
|
|
734
|
+
jsonResponse(res, 200, { ok: true, data: sessions });
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const sessionMatch = pathname.match(/^\/api\/sessions\/(.+)$/);
|
|
738
|
+
if (sessionMatch && method === "GET") {
|
|
739
|
+
const content = getSessionDetail(kmrDir, sessionMatch[1]);
|
|
740
|
+
if (content === null) {
|
|
741
|
+
jsonResponse(res, 404, { ok: false, error: "\u4F1A\u8BDD\u4E0D\u5B58\u5728" });
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
jsonResponse(res, 200, { ok: true, data: { userId: sessionMatch[1], content } });
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (sessionMatch && method === "DELETE") {
|
|
748
|
+
const deleted = deleteSession(kmrDir, sessionMatch[1]);
|
|
749
|
+
if (!deleted) {
|
|
750
|
+
jsonResponse(res, 404, { ok: false, error: "\u4F1A\u8BDD\u4E0D\u5B58\u5728" });
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
jsonResponse(res, 200, { ok: true, data: null });
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
jsonResponse(res, 404, { ok: false, error: "API \u8DEF\u7531\u4E0D\u5B58\u5728" });
|
|
757
|
+
} catch (err) {
|
|
758
|
+
jsonResponse(res, 500, { ok: false, error: err.message });
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/web/openBrowser.ts
|
|
763
|
+
import { execFile as execFile2 } from "child_process";
|
|
764
|
+
function openBrowser(url) {
|
|
765
|
+
const platform = process.platform;
|
|
766
|
+
switch (platform) {
|
|
767
|
+
case "darwin":
|
|
768
|
+
execFile2("open", [url]);
|
|
769
|
+
break;
|
|
770
|
+
case "win32":
|
|
771
|
+
execFile2("cmd", ["/c", "start", url]);
|
|
772
|
+
break;
|
|
773
|
+
default:
|
|
774
|
+
execFile2("xdg-open", [url]);
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// src/session/manager.ts
|
|
780
|
+
import { execFile as execFile3 } from "child_process";
|
|
781
|
+
import { promisify as promisify2 } from "util";
|
|
782
|
+
import fs4 from "fs";
|
|
783
|
+
import path4 from "path";
|
|
784
|
+
|
|
785
|
+
// src/session/skill.ts
|
|
786
|
+
var SESSION_SKILL = `\u4F60\u662F KMR\uFF08Key Meetings Record\uFF09\u7684\u667A\u80FD\u52A9\u624B\u3002
|
|
787
|
+
|
|
788
|
+
\u4F60\u7684\u804C\u8D23\uFF1A
|
|
789
|
+
1. \u5E2E\u52A9\u7528\u6237\u7406\u89E3\u548C\u7BA1\u7406\u4F1A\u8BAE\u8BB0\u5F55
|
|
790
|
+
2. \u56DE\u7B54\u5173\u4E8E\u5386\u53F2\u4F1A\u8BAE\u5185\u5BB9\u7684\u95EE\u9898
|
|
791
|
+
3. \u5E2E\u52A9\u7528\u6237\u6574\u7406\u3001\u603B\u7ED3\u5DE5\u4F5C\u4E2D\u7684\u4FE1\u606F
|
|
792
|
+
4. \u63D0\u4F9B\u5DE5\u4F5C\u5EFA\u8BAE\u548C\u77E5\u8BC6\u68C0\u7D22
|
|
793
|
+
|
|
794
|
+
\u80CC\u666F\u4FE1\u606F\uFF1A
|
|
795
|
+
- KMR \u662F\u4E00\u4E2A\u4ECE\u98DE\u4E66\u4F1A\u8BAE\u7EAA\u8981\u4E2D\u63D0\u53D6\u5173\u952E\u4FE1\u606F\u7684\u670D\u52A1
|
|
796
|
+
- \u6838\u5FC3\u63D0\u53D6\u7EF4\u5EA6\uFF1A\u5173\u952E\u5171\u8BC6\u4E0E\u627F\u8BFA\uFF08\u6EAF\u6E90\u8FFD\u8D23\uFF09\u3001\u53EF\u590D\u7528\u77E5\u8BC6\uFF08\u5DE5\u5177/\u65B9\u6CD5/\u7ECF\u9A8C\uFF09
|
|
797
|
+
- \u7528\u6237\u53EF\u4EE5\u901A\u8FC7 /find \u641C\u7D22\u5386\u53F2\u4F1A\u8BAE\uFF0C/listall \u5217\u51FA\u6240\u6709\u8BB0\u5F55\uFF0C/show \u67E5\u770B\u8BE6\u60C5\uFF0C/del \u5220\u9664\u8BB0\u5F55
|
|
798
|
+
|
|
799
|
+
\u5BF9\u8BDD\u8981\u6C42\uFF1A
|
|
800
|
+
- \u7B80\u6D01\u3001\u4E13\u4E1A\u3001\u6709\u5E2E\u52A9
|
|
801
|
+
- \u5982\u679C\u7528\u6237\u7684\u95EE\u9898\u6D89\u53CA\u4F1A\u8BAE\u8BB0\u5F55\u64CD\u4F5C\uFF0C\u5F15\u5BFC\u4ED6\u4EEC\u4F7F\u7528\u5BF9\u5E94\u7684\u547D\u4EE4
|
|
802
|
+
- \u6BCF\u6B21\u56DE\u590D\u63A7\u5236\u5728\u5408\u7406\u957F\u5EA6\uFF0C\u9002\u5408\u98DE\u4E66\u6D88\u606F\u9605\u8BFB
|
|
803
|
+
|
|
804
|
+
\u3010\u91CD\u8981\u3011\u76F4\u63A5\u8F93\u51FA\u7EAF\u6587\u672C\u56DE\u590D\uFF0C\u4E0D\u8981\u8F93\u51FA JSON\u3001markdown \u4EE3\u7801\u5757\u7B49\u683C\u5F0F\u3002`;
|
|
805
|
+
|
|
806
|
+
// src/session/manager.ts
|
|
807
|
+
var execFileAsync2 = promisify2(execFile3);
|
|
808
|
+
var SessionManager = class {
|
|
809
|
+
constructor(kmrDir, timeout = 6e4) {
|
|
810
|
+
this.timeout = timeout;
|
|
811
|
+
this.sessionDir = path4.join(kmrDir, "sessions");
|
|
812
|
+
fs4.mkdirSync(this.sessionDir, { recursive: true });
|
|
813
|
+
}
|
|
814
|
+
timeout;
|
|
815
|
+
activeSessions = /* @__PURE__ */ new Map();
|
|
816
|
+
sessionDir;
|
|
817
|
+
async handleMessage(userId, text) {
|
|
818
|
+
const sessionName = await this.ensureSession(userId);
|
|
819
|
+
console.log(`[session] \u53D1\u9001\u6D88\u606F\u5230 session: ${sessionName}`);
|
|
820
|
+
const reply = await this.sendToSession(sessionName, text);
|
|
821
|
+
await this.appendSummary(userId, text, reply);
|
|
822
|
+
return reply;
|
|
823
|
+
}
|
|
824
|
+
async ensureSession(userId) {
|
|
825
|
+
const existing = this.activeSessions.get(userId);
|
|
826
|
+
if (existing) {
|
|
827
|
+
console.log(`[session] \u590D\u7528\u5DF2\u6709 session: ${existing.name}`);
|
|
828
|
+
return existing.name;
|
|
829
|
+
}
|
|
830
|
+
const sessionName = `kmr-${userId}`;
|
|
831
|
+
const userDir = path4.join(this.sessionDir, userId);
|
|
832
|
+
fs4.mkdirSync(userDir, { recursive: true });
|
|
833
|
+
const skillPath = path4.join(userDir, "skill.md");
|
|
834
|
+
fs4.writeFileSync(skillPath, SESSION_SKILL, "utf-8");
|
|
835
|
+
console.log(`[session] \u521B\u5EFA\u65B0 session: ${sessionName}`);
|
|
836
|
+
try {
|
|
837
|
+
await execFileAsync2("acpx", ["claude", "sessions", "ensure", "--name", sessionName], {
|
|
838
|
+
timeout: this.timeout,
|
|
839
|
+
maxBuffer: 1024 * 1024
|
|
840
|
+
});
|
|
841
|
+
} catch (err) {
|
|
842
|
+
console.error(`[session] \u521B\u5EFA session \u5931\u8D25:`, err.message);
|
|
843
|
+
throw new Error(`\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25: ${err.message}`);
|
|
844
|
+
}
|
|
845
|
+
console.log(`[session] \u53D1\u9001\u89D2\u8272\u8BBE\u5B9A...`);
|
|
846
|
+
try {
|
|
847
|
+
await execFileAsync2(
|
|
848
|
+
"acpx",
|
|
849
|
+
["--approve-all", "claude", "-s", sessionName, SESSION_SKILL],
|
|
850
|
+
{ timeout: this.timeout, maxBuffer: 1024 * 1024 }
|
|
851
|
+
);
|
|
852
|
+
} catch (err) {
|
|
853
|
+
console.error(`[session] \u89D2\u8272\u8BBE\u5B9A\u5931\u8D25:`, err.message);
|
|
854
|
+
}
|
|
855
|
+
this.activeSessions.set(userId, {
|
|
856
|
+
name: sessionName,
|
|
857
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
858
|
+
});
|
|
859
|
+
return sessionName;
|
|
860
|
+
}
|
|
861
|
+
async sendToSession(sessionName, text) {
|
|
862
|
+
try {
|
|
863
|
+
const { stdout } = await execFileAsync2(
|
|
864
|
+
"acpx",
|
|
865
|
+
["--approve-all", "claude", "-s", sessionName, text],
|
|
866
|
+
{ timeout: this.timeout, maxBuffer: 1024 * 1024 }
|
|
867
|
+
);
|
|
868
|
+
return this.extractReply(stdout);
|
|
869
|
+
} catch (err) {
|
|
870
|
+
if (err.killed) {
|
|
871
|
+
throw new Error(`AI \u56DE\u590D\u8D85\u65F6 (${this.timeout}ms)`);
|
|
872
|
+
}
|
|
873
|
+
throw new Error(`AI \u56DE\u590D\u5931\u8D25: ${err.message}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
extractReply(output) {
|
|
877
|
+
const lines = output.split("\n");
|
|
878
|
+
const filtered = lines.filter((line) => {
|
|
879
|
+
const t = line.trim();
|
|
880
|
+
return t.length > 0 && !t.startsWith("[client]") && !t.startsWith("[tool]") && !t.startsWith("[thinking]") && !t.startsWith("[done]") && !t.startsWith("[error]") && !t.startsWith("[warn]") && !t.startsWith("[info]");
|
|
881
|
+
}).join("\n").trim();
|
|
882
|
+
return filtered || output.trim();
|
|
883
|
+
}
|
|
884
|
+
async appendSummary(userId, userText, aiReply) {
|
|
885
|
+
const userDir = path4.join(this.sessionDir, userId);
|
|
886
|
+
const summaryPath = path4.join(userDir, "summary.md");
|
|
887
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
888
|
+
const entry = `
|
|
889
|
+
[${now}] \u7528\u6237: ${userText}
|
|
890
|
+
[${now}] AI: ${aiReply}
|
|
891
|
+
`;
|
|
892
|
+
fs4.appendFileSync(summaryPath, entry, "utf-8");
|
|
893
|
+
}
|
|
894
|
+
async closeSession(userId) {
|
|
895
|
+
const info = this.activeSessions.get(userId);
|
|
896
|
+
if (!info) return;
|
|
897
|
+
try {
|
|
898
|
+
await execFileAsync2("acpx", ["claude", "sessions", "close", info.name], {
|
|
899
|
+
timeout: 1e4
|
|
900
|
+
});
|
|
901
|
+
console.log(`[session] \u5DF2\u5173\u95ED session: ${info.name}`);
|
|
902
|
+
} catch (err) {
|
|
903
|
+
console.error(`[session] \u5173\u95ED session \u5931\u8D25:`, err.message);
|
|
904
|
+
}
|
|
905
|
+
this.activeSessions.delete(userId);
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// src/lark/taskCreator.ts
|
|
910
|
+
var TaskCreator = class {
|
|
911
|
+
constructor(client) {
|
|
912
|
+
this.client = client;
|
|
913
|
+
}
|
|
914
|
+
client;
|
|
915
|
+
async createTask(params) {
|
|
916
|
+
const taskBody = {
|
|
917
|
+
summary: params.summary
|
|
918
|
+
};
|
|
919
|
+
if (params.due) {
|
|
920
|
+
const dueDate = /* @__PURE__ */ new Date(params.due + "T23:59:59+08:00");
|
|
921
|
+
taskBody.due = {
|
|
922
|
+
timestamp: dueDate.getTime().toString(),
|
|
923
|
+
is_all_day: true
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
if (params.description) {
|
|
927
|
+
taskBody.description = params.description;
|
|
928
|
+
}
|
|
929
|
+
if (params.assigneeOpenId) {
|
|
930
|
+
taskBody.members = [
|
|
931
|
+
{
|
|
932
|
+
id: params.assigneeOpenId,
|
|
933
|
+
type: "user",
|
|
934
|
+
role: "assignee"
|
|
935
|
+
}
|
|
936
|
+
];
|
|
937
|
+
}
|
|
938
|
+
console.log(`[task] \u521B\u5EFA\u98DE\u4E66\u4EFB\u52A1: ${params.summary}`);
|
|
939
|
+
console.log(`[task] \u8BF7\u6C42\u4F53:`, JSON.stringify(taskBody, null, 2));
|
|
940
|
+
const response = await this.client.task.v2.task.create({
|
|
941
|
+
params: { user_id_type: "open_id" },
|
|
942
|
+
data: taskBody
|
|
943
|
+
});
|
|
944
|
+
console.log(`[task] API \u8FD4\u56DE:`, JSON.stringify(response.data, null, 2));
|
|
945
|
+
const taskId = response.data?.task?.guid || "";
|
|
946
|
+
const url = response.data?.task?.url || "";
|
|
947
|
+
console.log(`[task] \u4EFB\u52A1\u521B\u5EFA\u6210\u529F: taskId=${taskId}`);
|
|
948
|
+
return { taskId, url };
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
// src/index.ts
|
|
13
953
|
async function main() {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
954
|
+
const config = loadConfig();
|
|
955
|
+
const client = createLarkClient(config);
|
|
956
|
+
const docReader = new DocReader(client);
|
|
957
|
+
const messenger = new Messenger(client);
|
|
958
|
+
const agent = new ClaudeCodeProvider(config.agent.timeout);
|
|
959
|
+
const store = new JsonStore(config.storage.dataDir);
|
|
960
|
+
const queryHandler = new QueryHandler(agent, config.storage.dataDir);
|
|
961
|
+
const sessionManager = new SessionManager(KMR_DIR, config.agent.timeout);
|
|
962
|
+
const taskCreator = new TaskCreator(client);
|
|
963
|
+
const pendingConfirmations = /* @__PURE__ */ new Map();
|
|
964
|
+
const dispatcher = createEventDispatcher(async (messageId, text, _chatId, senderId, meta) => {
|
|
965
|
+
const parsed = parseMessage(text);
|
|
966
|
+
console.log(`[route] \u6D88\u606F\u8DEF\u7531: type=${parsed.type}, messageId=${messageId}, isGroup=${meta.isGroup}, isMentioned=${meta.isMentioned}`);
|
|
967
|
+
if (meta.isGroup && !meta.isMentioned && parsed.type === "document_link" /* DOCUMENT_LINK */) {
|
|
968
|
+
const title = await docReader.getDocumentTitle(parsed.documentId);
|
|
969
|
+
console.log(`[filter] \u7FA4\u804A\u6587\u6863\u6807\u9898: "${title}"`);
|
|
970
|
+
const isMeetingNote = /纪要|会议记录|meeting/i.test(title) || /from=vc_assistant/.test(text);
|
|
971
|
+
if (!isMeetingNote) {
|
|
972
|
+
console.log(`[filter] \u6587\u6863\u6807\u9898\u4E0D\u542B\u7EAA\u8981\u5173\u952E\u8BCD, \u5FFD\u7565`);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
console.log(`[filter] \u786E\u8BA4\u4E3A\u4F1A\u8BAE\u7EAA\u8981, \u7EE7\u7EED\u5904\u7406`);
|
|
976
|
+
}
|
|
977
|
+
try {
|
|
978
|
+
switch (parsed.type) {
|
|
979
|
+
case "document_link" /* DOCUMENT_LINK */: {
|
|
980
|
+
console.log(`[process] \u5F00\u59CB\u5904\u7406\u6587\u6863\u94FE\u63A5, documentId=${parsed.documentId}`);
|
|
981
|
+
await messenger.replyText(messageId, "\u6B63\u5728\u63D0\u53D6\u4F1A\u8BAE\u5173\u952E\u4FE1\u606F\uFF0C\u8BF7\u7A0D\u5019...");
|
|
982
|
+
console.log(`[reply] \u5DF2\u53D1\u9001"\u6B63\u5728\u63D0\u53D6"\u63D0\u793A`);
|
|
983
|
+
console.log(`[process] \u8BFB\u53D6\u98DE\u4E66\u6587\u6863\u5185\u5BB9...`);
|
|
984
|
+
const content = await docReader.readDocument(parsed.documentId);
|
|
985
|
+
console.log(`[process] \u6587\u6863\u5185\u5BB9\u8BFB\u53D6\u5B8C\u6210, \u957F\u5EA6=${content.length}`);
|
|
986
|
+
console.log(`[process] \u8C03\u7528 Agent \u63D0\u53D6\u4F1A\u8BAE\u4FE1\u606F...`);
|
|
987
|
+
const record = await agent.extract(content);
|
|
988
|
+
console.log(`[process] Agent \u63D0\u53D6\u5B8C\u6210: title="${record.summary.title}"`);
|
|
989
|
+
record.documentUrl = text.match(/(https?:\/\/[^\s]*feishu\.cn\/[^\s]+)/)?.[0] || "";
|
|
990
|
+
await store.save(record);
|
|
991
|
+
console.log(`\u2705 \u4F1A\u8BAE\u63D0\u53D6\u6210\u529F: ${record.summary.title} (${record.id})`);
|
|
992
|
+
await messenger.replyMeetingSummary(messageId, record);
|
|
993
|
+
console.log(`[reply] \u5DF2\u53D1\u9001\u4F1A\u8BAE\u6458\u8981\u56DE\u590D`);
|
|
994
|
+
if (record.todos.length > 0) {
|
|
995
|
+
const oldPending = pendingConfirmations.get(senderId);
|
|
996
|
+
if (oldPending) clearTimeout(oldPending.timer);
|
|
997
|
+
const timer = setTimeout(() => {
|
|
998
|
+
const p = pendingConfirmations.get(senderId);
|
|
999
|
+
if (p && p.meetingId === record.id) {
|
|
1000
|
+
pendingConfirmations.delete(senderId);
|
|
1001
|
+
console.log(`[timeout] \u5F85\u529E\u786E\u8BA4\u8D85\u65F6, userId=${senderId}, meetingId=${record.id}`);
|
|
1002
|
+
}
|
|
1003
|
+
}, 6e4);
|
|
1004
|
+
pendingConfirmations.set(senderId, {
|
|
1005
|
+
meetingId: record.id,
|
|
1006
|
+
todos: record.todos,
|
|
1007
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1008
|
+
timer
|
|
1009
|
+
});
|
|
1010
|
+
await messenger.replyTodoConfirmation(messageId, record.todos);
|
|
1011
|
+
console.log(`[reply] \u5DF2\u53D1\u9001\u5F85\u529E\u786E\u8BA4\u6D88\u606F, ${record.todos.length} \u6761\u5F85\u529E`);
|
|
1012
|
+
}
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
1015
|
+
case "find_query" /* FIND_QUERY */: {
|
|
1016
|
+
console.log(`[process] \u5F00\u59CB\u641C\u7D22: query="${parsed.query}"`);
|
|
1017
|
+
const results = await queryHandler.find(parsed.query);
|
|
1018
|
+
console.log(`[process] \u641C\u7D22\u5B8C\u6210, \u7ED3\u679C\u6570=${results.length}`);
|
|
1019
|
+
await messenger.replySearchResults(messageId, results);
|
|
1020
|
+
console.log(`[reply] \u5DF2\u53D1\u9001\u641C\u7D22\u7ED3\u679C`);
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
case "list_all" /* LIST_ALL */: {
|
|
1024
|
+
console.log(`[process] \u5217\u51FA\u6240\u6709\u8BB0\u5F55`);
|
|
1025
|
+
const all = await store.list();
|
|
1026
|
+
console.log(`[process] \u5171 ${all.length} \u6761\u8BB0\u5F55`);
|
|
1027
|
+
await messenger.replyRecordList(messageId, all);
|
|
1028
|
+
console.log(`[reply] \u5DF2\u53D1\u9001\u8BB0\u5F55\u5217\u8868`);
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
case "delete_record" /* DELETE_RECORD */: {
|
|
1032
|
+
console.log(`[process] \u5220\u9664\u8BB0\u5F55: id=${parsed.deleteId}`);
|
|
1033
|
+
const deleted = await store.delete(parsed.deleteId);
|
|
1034
|
+
if (deleted) {
|
|
1035
|
+
console.log(`\u2705 \u8BB0\u5F55\u5DF2\u5220\u9664: ${parsed.deleteId}`);
|
|
1036
|
+
await messenger.replyText(messageId, `\u2705 \u5DF2\u5220\u9664\u8BB0\u5F55: ${parsed.deleteId}`);
|
|
1037
|
+
} else {
|
|
1038
|
+
await messenger.replyText(messageId, `\u274C \u672A\u627E\u5230\u8BB0\u5F55: ${parsed.deleteId}`);
|
|
1039
|
+
}
|
|
1040
|
+
console.log(`[reply] \u5DF2\u53D1\u9001\u5220\u9664\u7ED3\u679C`);
|
|
1041
|
+
break;
|
|
37
1042
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
case MessageType.DELETE_RECORD: {
|
|
96
|
-
console.log(`[process] 删除记录: id=${parsed.deleteId}`);
|
|
97
|
-
const deleted = await store.delete(parsed.deleteId);
|
|
98
|
-
if (deleted) {
|
|
99
|
-
console.log(`✅ 记录已删除: ${parsed.deleteId}`);
|
|
100
|
-
await messenger.replyText(messageId, `✅ 已删除记录: ${parsed.deleteId}`);
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
await messenger.replyText(messageId, `❌ 未找到记录: ${parsed.deleteId}`);
|
|
104
|
-
}
|
|
105
|
-
console.log(`[reply] 已发送删除结果`);
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
case MessageType.SHOW_DETAIL: {
|
|
109
|
-
console.log(`[process] 查看详情: id=${parsed.showId}`);
|
|
110
|
-
const detail = await store.load(parsed.showId);
|
|
111
|
-
if (detail) {
|
|
112
|
-
await messenger.replyRecordDetail(messageId, detail);
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
await messenger.replyText(messageId, `❌ 未找到记录: ${parsed.showId}`);
|
|
116
|
-
}
|
|
117
|
-
console.log(`[reply] 已发送详情`);
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
case MessageType.CONFIRM_TASKS: {
|
|
121
|
-
const pending = pendingConfirmations.get(senderId);
|
|
122
|
-
if (!pending) {
|
|
123
|
-
await messenger.replyText(messageId, '没有待确认的待办事项。请先发送会议文档链接提取待办。');
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
clearTimeout(pending.timer);
|
|
127
|
-
const selectedTodos = parsed.confirmIds === 'all'
|
|
128
|
-
? pending.todos
|
|
129
|
-
: pending.todos.filter((_, i) => parsed.confirmIds.includes(i + 1));
|
|
130
|
-
if (selectedTodos.length === 0) {
|
|
131
|
-
await messenger.replyText(messageId, '未选中有效的待办编号,请检查后重试。');
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
console.log(`[process] 创建飞书任务: ${selectedTodos.length} 条`);
|
|
135
|
-
await messenger.replyText(messageId, `正在创建 ${selectedTodos.length} 条飞书任务...`);
|
|
136
|
-
const results = [];
|
|
137
|
-
for (const todo of selectedTodos) {
|
|
138
|
-
try {
|
|
139
|
-
const result = await taskCreator.createTask({
|
|
140
|
-
summary: `${todo.content}(${todo.owner})`,
|
|
141
|
-
due: todo.deadline,
|
|
142
|
-
description: `来源会议: ${pending.meetingId}\n负责人: ${todo.owner}`,
|
|
143
|
-
assigneeOpenId: senderId,
|
|
144
|
-
});
|
|
145
|
-
results.push({ summary: todo.content, success: true, taskId: result.taskId, url: result.url });
|
|
146
|
-
console.log(`✅ 任务创建成功: ${todo.content}`);
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
results.push({ summary: todo.content, success: false, error: err.message });
|
|
150
|
-
console.error(`❌ 任务创建失败: ${todo.content}`, err.message);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
await messenger.replyTaskResults(messageId, results);
|
|
154
|
-
// 将创建的任务关联回会议记录
|
|
155
|
-
const createdTasks = results
|
|
156
|
-
.filter((r) => r.success)
|
|
157
|
-
.map((r) => ({
|
|
158
|
-
summary: r.summary,
|
|
159
|
-
taskId: r.taskId || '',
|
|
160
|
-
url: r.url || '',
|
|
161
|
-
createdAt: new Date().toISOString(),
|
|
162
|
-
}));
|
|
163
|
-
if (createdTasks.length > 0) {
|
|
164
|
-
const record = await store.load(pending.meetingId);
|
|
165
|
-
if (record) {
|
|
166
|
-
record.createdTasks = [...(record.createdTasks || []), ...createdTasks];
|
|
167
|
-
await store.save(record);
|
|
168
|
-
console.log(`[task] 已将 ${createdTasks.length} 条任务关联到会议 ${pending.meetingId}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
pendingConfirmations.delete(senderId);
|
|
172
|
-
console.log(`[reply] 已发送任务创建结果`);
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
case MessageType.REJECT_TASKS: {
|
|
176
|
-
const pending = pendingConfirmations.get(senderId);
|
|
177
|
-
if (!pending) {
|
|
178
|
-
await messenger.replyText(messageId, '没有待确认的待办事项。');
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
clearTimeout(pending.timer);
|
|
182
|
-
pendingConfirmations.delete(senderId);
|
|
183
|
-
await messenger.replyText(messageId, '✅ 已取消创建待办任务。');
|
|
184
|
-
console.log(`[process] 用户拒绝创建任务, userId=${senderId}`);
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
case MessageType.UNKNOWN: {
|
|
188
|
-
// 群聊非@消息不触发 AI 对话
|
|
189
|
-
if (meta.isGroup && !meta.isMentioned) {
|
|
190
|
-
console.log(`[filter] 群聊非@未知消息, 忽略`);
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
console.log(`[process] 自由对话, userId=${senderId}`);
|
|
194
|
-
await messenger.replyText(messageId, '正在思考...');
|
|
195
|
-
const reply = await sessionManager.handleMessage(senderId, text);
|
|
196
|
-
await messenger.replyText(messageId, reply);
|
|
197
|
-
console.log(`[reply] 已发送 AI 回复`);
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
1043
|
+
case "show_detail" /* SHOW_DETAIL */: {
|
|
1044
|
+
console.log(`[process] \u67E5\u770B\u8BE6\u60C5: id=${parsed.showId}`);
|
|
1045
|
+
const detail = await store.load(parsed.showId);
|
|
1046
|
+
if (detail) {
|
|
1047
|
+
await messenger.replyRecordDetail(messageId, detail);
|
|
1048
|
+
} else {
|
|
1049
|
+
await messenger.replyText(messageId, `\u274C \u672A\u627E\u5230\u8BB0\u5F55: ${parsed.showId}`);
|
|
1050
|
+
}
|
|
1051
|
+
console.log(`[reply] \u5DF2\u53D1\u9001\u8BE6\u60C5`);
|
|
1052
|
+
break;
|
|
1053
|
+
}
|
|
1054
|
+
case "confirm_tasks" /* CONFIRM_TASKS */: {
|
|
1055
|
+
const pending = pendingConfirmations.get(senderId);
|
|
1056
|
+
if (!pending) {
|
|
1057
|
+
await messenger.replyText(messageId, "\u6CA1\u6709\u5F85\u786E\u8BA4\u7684\u5F85\u529E\u4E8B\u9879\u3002\u8BF7\u5148\u53D1\u9001\u4F1A\u8BAE\u6587\u6863\u94FE\u63A5\u63D0\u53D6\u5F85\u529E\u3002");
|
|
1058
|
+
break;
|
|
1059
|
+
}
|
|
1060
|
+
clearTimeout(pending.timer);
|
|
1061
|
+
const selectedTodos = parsed.confirmIds === "all" ? pending.todos : pending.todos.filter((_, i) => parsed.confirmIds.includes(i + 1));
|
|
1062
|
+
if (selectedTodos.length === 0) {
|
|
1063
|
+
await messenger.replyText(messageId, "\u672A\u9009\u4E2D\u6709\u6548\u7684\u5F85\u529E\u7F16\u53F7\uFF0C\u8BF7\u68C0\u67E5\u540E\u91CD\u8BD5\u3002");
|
|
1064
|
+
break;
|
|
1065
|
+
}
|
|
1066
|
+
console.log(`[process] \u521B\u5EFA\u98DE\u4E66\u4EFB\u52A1: ${selectedTodos.length} \u6761`);
|
|
1067
|
+
await messenger.replyText(messageId, `\u6B63\u5728\u521B\u5EFA ${selectedTodos.length} \u6761\u98DE\u4E66\u4EFB\u52A1...`);
|
|
1068
|
+
const results = [];
|
|
1069
|
+
for (const todo of selectedTodos) {
|
|
1070
|
+
try {
|
|
1071
|
+
const result = await taskCreator.createTask({
|
|
1072
|
+
summary: `${todo.content}\uFF08${todo.owner}\uFF09`,
|
|
1073
|
+
due: todo.deadline,
|
|
1074
|
+
description: `\u6765\u6E90\u4F1A\u8BAE: ${pending.meetingId}
|
|
1075
|
+
\u8D1F\u8D23\u4EBA: ${todo.owner}`,
|
|
1076
|
+
assigneeOpenId: senderId
|
|
1077
|
+
});
|
|
1078
|
+
results.push({ summary: todo.content, success: true, taskId: result.taskId, url: result.url });
|
|
1079
|
+
console.log(`\u2705 \u4EFB\u52A1\u521B\u5EFA\u6210\u529F: ${todo.content}`);
|
|
1080
|
+
} catch (err) {
|
|
1081
|
+
results.push({ summary: todo.content, success: false, error: err.message });
|
|
1082
|
+
console.error(`\u274C \u4EFB\u52A1\u521B\u5EFA\u5931\u8D25: ${todo.content}`, err.message);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
await messenger.replyTaskResults(messageId, results);
|
|
1086
|
+
const createdTasks = results.filter((r) => r.success).map((r) => ({
|
|
1087
|
+
summary: r.summary,
|
|
1088
|
+
taskId: r.taskId || "",
|
|
1089
|
+
url: r.url || "",
|
|
1090
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1091
|
+
}));
|
|
1092
|
+
if (createdTasks.length > 0) {
|
|
1093
|
+
const record = await store.load(pending.meetingId);
|
|
1094
|
+
if (record) {
|
|
1095
|
+
record.createdTasks = [...record.createdTasks || [], ...createdTasks];
|
|
1096
|
+
await store.save(record);
|
|
1097
|
+
console.log(`[task] \u5DF2\u5C06 ${createdTasks.length} \u6761\u4EFB\u52A1\u5173\u8054\u5230\u4F1A\u8BAE ${pending.meetingId}`);
|
|
200
1098
|
}
|
|
1099
|
+
}
|
|
1100
|
+
pendingConfirmations.delete(senderId);
|
|
1101
|
+
console.log(`[reply] \u5DF2\u53D1\u9001\u4EFB\u52A1\u521B\u5EFA\u7ED3\u679C`);
|
|
1102
|
+
break;
|
|
201
1103
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
1104
|
+
case "reject_tasks" /* REJECT_TASKS */: {
|
|
1105
|
+
const pending = pendingConfirmations.get(senderId);
|
|
1106
|
+
if (!pending) {
|
|
1107
|
+
await messenger.replyText(messageId, "\u6CA1\u6709\u5F85\u786E\u8BA4\u7684\u5F85\u529E\u4E8B\u9879\u3002");
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
clearTimeout(pending.timer);
|
|
1111
|
+
pendingConfirmations.delete(senderId);
|
|
1112
|
+
await messenger.replyText(messageId, "\u2705 \u5DF2\u53D6\u6D88\u521B\u5EFA\u5F85\u529E\u4EFB\u52A1\u3002");
|
|
1113
|
+
console.log(`[process] \u7528\u6237\u62D2\u7EDD\u521B\u5EFA\u4EFB\u52A1, userId=${senderId}`);
|
|
1114
|
+
break;
|
|
206
1115
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
1116
|
+
case "unknown" /* UNKNOWN */: {
|
|
1117
|
+
if (meta.isGroup && !meta.isMentioned) {
|
|
1118
|
+
console.log(`[filter] \u7FA4\u804A\u975E@\u672A\u77E5\u6D88\u606F, \u5FFD\u7565`);
|
|
1119
|
+
break;
|
|
1120
|
+
}
|
|
1121
|
+
console.log(`[process] \u81EA\u7531\u5BF9\u8BDD, userId=${senderId}`);
|
|
1122
|
+
await messenger.replyText(messageId, "\u6B63\u5728\u601D\u8003...");
|
|
1123
|
+
const reply = await sessionManager.handleMessage(senderId, text);
|
|
1124
|
+
await messenger.replyText(messageId, reply);
|
|
1125
|
+
console.log(`[reply] \u5DF2\u53D1\u9001 AI \u56DE\u590D`);
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
} catch (err) {
|
|
1130
|
+
console.error("[error] \u5904\u7406\u6D88\u606F\u5931\u8D25:", err);
|
|
1131
|
+
await messenger.replyText(messageId, `\u5904\u7406\u5931\u8D25: ${err.message}`);
|
|
1132
|
+
console.log(`[reply] \u5DF2\u53D1\u9001\u9519\u8BEF\u56DE\u590D`);
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
startWSClient(client, dispatcher);
|
|
1136
|
+
const { port } = await startWebServer(store, KMR_DIR, 3e3);
|
|
1137
|
+
openBrowser(`http://localhost:${port}`);
|
|
1138
|
+
console.log(`KMR Web \u754C\u9762\u5DF2\u542F\u52A8: http://localhost:${port}`);
|
|
1139
|
+
console.log("KMR \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7B49\u5F85\u98DE\u4E66\u6D88\u606F...");
|
|
213
1140
|
}
|
|
214
1141
|
main().catch(console.error);
|