@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.
Files changed (49) hide show
  1. package/dist/chunk-X36FOW5Y.js +47 -0
  2. package/dist/cli.js +51 -34
  3. package/dist/config-KM4HTJA2.js +12 -0
  4. package/dist/index.js +1134 -207
  5. package/package.json +11 -3
  6. package/dist/agent/claudeCode.d.ts +0 -12
  7. package/dist/agent/claudeCode.js +0 -109
  8. package/dist/agent/prompt.d.ts +0 -3
  9. package/dist/agent/prompt.js +0 -33
  10. package/dist/agent/types.d.ts +0 -7
  11. package/dist/agent/types.js +0 -1
  12. package/dist/cli-init.d.ts +0 -1
  13. package/dist/cli-init.js +0 -12
  14. package/dist/cli.d.ts +0 -2
  15. package/dist/config.d.ts +0 -17
  16. package/dist/config.js +0 -38
  17. package/dist/index.d.ts +0 -1
  18. package/dist/lark/client.d.ts +0 -8
  19. package/dist/lark/client.js +0 -68
  20. package/dist/lark/docReader.d.ts +0 -9
  21. package/dist/lark/docReader.js +0 -75
  22. package/dist/lark/messenger.d.ts +0 -22
  23. package/dist/lark/messenger.js +0 -156
  24. package/dist/lark/router.d.ts +0 -20
  25. package/dist/lark/router.js +0 -75
  26. package/dist/lark/taskCreator.d.ts +0 -15
  27. package/dist/lark/taskCreator.js +0 -41
  28. package/dist/query/finder.d.ts +0 -2
  29. package/dist/query/finder.js +0 -18
  30. package/dist/query/handler.d.ts +0 -8
  31. package/dist/query/handler.js +0 -17
  32. package/dist/session/manager.d.ts +0 -12
  33. package/dist/session/manager.js +0 -114
  34. package/dist/session/skill.d.ts +0 -1
  35. package/dist/session/skill.js +0 -19
  36. package/dist/storage/jsonStore.d.ts +0 -11
  37. package/dist/storage/jsonStore.js +0 -51
  38. package/dist/storage/types.d.ts +0 -52
  39. package/dist/storage/types.js +0 -1
  40. package/dist/web/openBrowser.d.ts +0 -1
  41. package/dist/web/openBrowser.js +0 -15
  42. package/dist/web/public/public/app.js +0 -344
  43. package/dist/web/public/public/index.html +0 -28
  44. package/dist/web/public/style.css +0 -428
  45. package/dist/web/server.d.ts +0 -6
  46. package/dist/web/server.js +0 -209
  47. /package/dist/{web → public}/public/app.js +0 -0
  48. /package/dist/{web → public}/public/index.html +0 -0
  49. /package/dist/{web/public → public}/public/style.css +0 -0
package/dist/index.js CHANGED
@@ -1,214 +1,1141 @@
1
- import { loadConfig, KMR_DIR } from './config';
2
- import { createLarkClient, createEventDispatcher, startWSClient } from './lark/client';
3
- import { DocReader } from './lark/docReader';
4
- import { Messenger } from './lark/messenger';
5
- import { parseMessage, MessageType } from './lark/router';
6
- import { ClaudeCodeProvider } from './agent/claudeCode';
7
- import { JsonStore } from './storage/jsonStore';
8
- import { QueryHandler } from './query/handler';
9
- import { startWebServer } from './web/server';
10
- import { openBrowser } from './web/openBrowser';
11
- import { SessionManager } from './session/manager';
12
- import { TaskCreator } from './lark/taskCreator';
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
- const config = loadConfig();
15
- const client = createLarkClient(config);
16
- const docReader = new DocReader(client);
17
- const messenger = new Messenger(client);
18
- const agent = new ClaudeCodeProvider(config.agent.timeout);
19
- const store = new JsonStore(config.storage.dataDir);
20
- const queryHandler = new QueryHandler(agent, config.storage.dataDir);
21
- const sessionManager = new SessionManager(KMR_DIR, config.agent.timeout);
22
- const taskCreator = new TaskCreator(client);
23
- const pendingConfirmations = new Map();
24
- const dispatcher = createEventDispatcher(async (messageId, text, _chatId, senderId, meta) => {
25
- const parsed = parseMessage(text);
26
- console.log(`[route] 消息路由: type=${parsed.type}, messageId=${messageId}, isGroup=${meta.isGroup}, isMentioned=${meta.isMentioned}`);
27
- // 群聊非@消息 + 文档链接:需要验证文档标题是否为会议纪要
28
- if (meta.isGroup && !meta.isMentioned && parsed.type === MessageType.DOCUMENT_LINK) {
29
- const title = await docReader.getDocumentTitle(parsed.documentId);
30
- console.log(`[filter] 群聊文档标题: "${title}"`);
31
- const isMeetingNote = /纪要|会议记录|meeting/i.test(title) || /from=vc_assistant/.test(text);
32
- if (!isMeetingNote) {
33
- console.log(`[filter] 文档标题不含纪要关键词, 忽略`);
34
- return;
35
- }
36
- console.log(`[filter] 确认为会议纪要, 继续处理`);
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
- try {
39
- switch (parsed.type) {
40
- case MessageType.DOCUMENT_LINK: {
41
- console.log(`[process] 开始处理文档链接, documentId=${parsed.documentId}`);
42
- await messenger.replyText(messageId, '正在提取会议关键信息,请稍候...');
43
- console.log(`[reply] 已发送"正在提取"提示`);
44
- console.log(`[process] 读取飞书文档内容...`);
45
- const content = await docReader.readDocument(parsed.documentId);
46
- console.log(`[process] 文档内容读取完成, 长度=${content.length}`);
47
- console.log(`[process] 调用 Agent 提取会议信息...`);
48
- const record = await agent.extract(content);
49
- console.log(`[process] Agent 提取完成: title="${record.summary.title}"`);
50
- record.documentUrl = text.match(/(https?:\/\/[^\s]*feishu\.cn\/[^\s]+)/)?.[0] || '';
51
- await store.save(record);
52
- console.log(`✅ 会议提取成功: ${record.summary.title} (${record.id})`);
53
- await messenger.replyMeetingSummary(messageId, record);
54
- console.log(`[reply] 已发送会议摘要回复`);
55
- // 如果有待办,发确认消息
56
- if (record.todos.length > 0) {
57
- // 清理旧的待确认(如果有)
58
- const oldPending = pendingConfirmations.get(senderId);
59
- if (oldPending)
60
- clearTimeout(oldPending.timer);
61
- const timer = setTimeout(() => {
62
- const p = pendingConfirmations.get(senderId);
63
- if (p && p.meetingId === record.id) {
64
- pendingConfirmations.delete(senderId);
65
- console.log(`[timeout] 待办确认超时, userId=${senderId}, meetingId=${record.id}`);
66
- }
67
- }, 60_000);
68
- pendingConfirmations.set(senderId, {
69
- meetingId: record.id,
70
- todos: record.todos,
71
- createdAt: new Date().toISOString(),
72
- timer,
73
- });
74
- await messenger.replyTodoConfirmation(messageId, record.todos);
75
- console.log(`[reply] 已发送待办确认消息, ${record.todos.length} 条待办`);
76
- }
77
- break;
78
- }
79
- case MessageType.FIND_QUERY: {
80
- console.log(`[process] 开始搜索: query="${parsed.query}"`);
81
- const results = await queryHandler.find(parsed.query);
82
- console.log(`[process] 搜索完成, 结果数=${results.length}`);
83
- await messenger.replySearchResults(messageId, results);
84
- console.log(`[reply] 已发送搜索结果`);
85
- break;
86
- }
87
- case MessageType.LIST_ALL: {
88
- console.log(`[process] 列出所有记录`);
89
- const all = await store.list();
90
- console.log(`[process] ${all.length} 条记录`);
91
- await messenger.replyRecordList(messageId, all);
92
- console.log(`[reply] 已发送记录列表`);
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
- catch (err) {
203
- console.error('[error] 处理消息失败:', err);
204
- await messenger.replyText(messageId, `处理失败: ${err.message}`);
205
- console.log(`[reply] 已发送错误回复`);
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
- startWSClient(client, dispatcher);
209
- const { port } = await startWebServer(store, KMR_DIR, 3000);
210
- openBrowser(`http://localhost:${port}`);
211
- console.log(`KMR Web 界面已启动: http://localhost:${port}`);
212
- console.log('KMR 服务已启动,等待飞书消息...');
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);