chattercatcher 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/config/store.ts","../src/config/schema.ts","../src/config/paths.ts","../src/config/update.ts","../src/data/deletion.ts","../src/db/database.ts","../src/doctor/checks.ts","../src/files/jobs.ts","../src/gateway/runtime.ts","../src/gateway/index.ts","../src/llm/openai-compatible.ts","../src/messages/repository.ts","../src/messages/chunker.ts","../src/rag/hybrid-retriever.ts","../src/rag/message-retriever.ts","../src/rag/embedding.ts","../src/rag/sqlite-vector-store.ts","../src/rag/vector-retriever.ts","../src/rag/factory.ts","../src/export/data-export.ts","../src/export/data-restore.ts","../src/feishu/gateway.ts","../src/rag/citations.ts","../src/rag/answer.ts","../src/rag/qa-service.ts","../src/feishu/question.ts","../src/feishu/sender.ts","../src/feishu/resource-downloader.ts","../src/files/ingest.ts","../src/files/parser.ts","../src/feishu/normalize.ts","../src/gateway/ingest.ts","../src/logs/reader.ts","../src/rag/indexer.ts","../src/rag/manual-index.ts","../src/web/server.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { input, password, select, confirm, number } from \"@inquirer/prompts\";\nimport { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport type { AppConfig, AppSecrets } from \"./config/schema.js\";\nimport { loadConfig, loadSecrets, resetConfigFiles, saveConfig, saveSecrets, ensureConfigFiles, maskSecret } from \"./config/store.js\";\nimport { applySecretInput, resolveEmbeddingApiKey } from \"./config/update.js\";\nimport { getChatterCatcherHome, getConfigPath, getSecretsPath } from \"./config/paths.js\";\nimport { deleteLocalData, type DeleteTargetType } from \"./data/deletion.js\";\nimport { getDatabasePath, openDatabase } from \"./db/database.js\";\nimport { formatDoctorChecks, runDoctor } from \"./doctor/checks.js\";\nimport { exportLocalData } from \"./export/data-export.js\";\nimport { restoreLocalData } from \"./export/data-restore.js\";\nimport { createFeishuGateway } from \"./feishu/gateway.js\";\nimport type { FeishuReceiveMessageEvent } from \"./feishu/normalize.js\";\nimport { FeishuQuestionHandler } from \"./feishu/question.js\";\nimport { FeishuResourceDownloader } from \"./feishu/resource-downloader.js\";\nimport { FeishuMessageSender } from \"./feishu/sender.js\";\nimport { ingestLocalFile } from \"./files/ingest.js\";\nimport { FileJobRepository } from \"./files/jobs.js\";\nimport { GatewayIngestor } from \"./gateway/ingest.js\";\nimport { getGatewayStatus } from \"./gateway/index.js\";\nimport { removeGatewayPidRecord, stopGatewayProcess, writeGatewayPidRecord } from \"./gateway/runtime.js\";\nimport { createChatModel, createEmbeddingModel } from \"./llm/openai-compatible.js\";\nimport { followLogFile, getLogsDirectory, normalizeLineCount, readLatestLogTail } from \"./logs/reader.js\";\nimport { MessageRepository } from \"./messages/repository.js\";\nimport { createHybridRetriever, hasEmbeddingConfig } from \"./rag/factory.js\";\nimport { indexMessageChunks } from \"./rag/indexer.js\";\nimport { processMessagesNow } from \"./rag/manual-index.js\";\nimport { askWithRag } from \"./rag/qa-service.js\";\nimport { formatCitation } from \"./rag/citations.js\";\nimport { SQLiteVectorStore } from \"./rag/sqlite-vector-store.js\";\nimport { startWebServer } from \"./web/server.js\";\n\nconst program = new Command();\n\nasync function promptForConfiguration(config: AppConfig, secrets: AppSecrets): Promise<void> {\n config.feishu.domain = await select({\n message: \"选择飞书区域\",\n choices: [\n { name: \"飞书(中国)\", value: \"feishu\" as const },\n { name: \"Lark(国际)\", value: \"lark\" as const },\n ],\n default: config.feishu.domain,\n });\n config.feishu.appId = await input({ message: \"飞书 App ID\", default: config.feishu.appId });\n secrets.feishu.appSecret = applySecretInput(\n secrets.feishu.appSecret,\n await password({ message: secrets.feishu.appSecret ? \"飞书 App Secret(留空保留)\" : \"飞书 App Secret\", mask: \"*\" }),\n );\n\n config.llm.baseUrl = await input({ message: \"LLM Base URL(OpenAI-compatible)\", default: config.llm.baseUrl });\n secrets.llm.apiKey = applySecretInput(\n secrets.llm.apiKey,\n await password({ message: secrets.llm.apiKey ? \"LLM API Key(留空保留)\" : \"LLM API Key\", mask: \"*\" }),\n );\n config.llm.model = await input({ message: \"Chat Model\", default: config.llm.model });\n\n config.embedding.baseUrl = await input({\n message: \"Embedding Base URL(留空则使用 LLM Base URL)\",\n default: config.embedding.baseUrl || config.llm.baseUrl,\n });\n secrets.embedding.apiKey = resolveEmbeddingApiKey({\n currentEmbeddingKey: secrets.embedding.apiKey,\n nextEmbeddingKey: await password({\n message: secrets.embedding.apiKey ? \"Embedding API Key(留空保留)\" : \"Embedding API Key(留空则使用 LLM API Key)\",\n mask: \"*\",\n }),\n llmApiKey: secrets.llm.apiKey,\n });\n config.embedding.model = await input({ message: \"Embedding Model\", default: config.embedding.model });\n const dimension = await number({\n message: \"Embedding 维度(不知道可先留空)\",\n default: config.embedding.dimension ?? undefined,\n required: false,\n });\n config.embedding.dimension = dimension ?? null;\n\n config.web.port =\n (await number({ message: \"Web UI 端口\", default: config.web.port, required: true })) ?? config.web.port;\n config.feishu.requireMention = await confirm({\n message: \"群聊回答是否要求 @ChatterCatcher?\",\n default: config.feishu.requireMention,\n });\n}\n\nfunction printSettings(config: AppConfig, secrets: AppSecrets): void {\n console.log(JSON.stringify(\n {\n home: getChatterCatcherHome(),\n config,\n secrets: {\n feishu: { appSecret: maskSecret(secrets.feishu.appSecret) },\n llm: { apiKey: maskSecret(secrets.llm.apiKey) },\n embedding: { apiKey: maskSecret(secrets.embedding.apiKey) },\n },\n },\n null,\n 2,\n ));\n}\n\nprogram\n .name(\"chattercatcher\")\n .description(\"本地优先的飞书/Lark 家庭群知识机器人\")\n .version(\"0.1.6\");\n\nprogram.command(\"setup\").description(\"交互式初始化配置\").action(async () => {\n const { config, secrets } = await ensureConfigFiles();\n await promptForConfiguration(config, secrets);\n await saveConfig(config);\n await saveSecrets(secrets);\n console.log(`配置已保存:${getConfigPath()}`);\n console.log(`密钥已保存:${getSecretsPath()}`);\n});\n\nconst settings = program.command(\"settings\").description(\"查看或修改配置\");\n\nsettings.action(async () => {\n const { config, secrets } = await ensureConfigFiles();\n await promptForConfiguration(config, secrets);\n await saveConfig(config);\n await saveSecrets(secrets);\n console.log(`配置已更新:${getConfigPath()}`);\n console.log(`密钥已更新:${getSecretsPath()}`);\n});\n\nsettings.command(\"show\").description(\"查看当前配置(密钥脱敏)\").action(async () => {\n printSettings(await loadConfig(), await loadSecrets());\n});\n\nsettings.command(\"reset\").description(\"重置本地配置和密钥\").action(async () => {\n const shouldReset = await confirm({ message: \"确认重置 ChatterCatcher 配置?\", default: false });\n if (!shouldReset) {\n console.log(\"已取消。\");\n return;\n }\n\n await resetConfigFiles();\n console.log(\"配置已重置。\");\n});\n\nprogram.command(\"doctor\").description(\"检查本地配置、存储和可选在线连通性\").option(\"--online\", \"检查 LLM 和 Embedding 接口连通性\").action(async (options: { online?: boolean }) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const checks = await runDoctor(config, secrets, { online: options.online });\n console.log(formatDoctorChecks(checks));\n});\n\nconst gateway = program.command(\"gateway\").description(\"管理本地飞书 Gateway\");\n\nasync function startGatewayCommand(): Promise<void> {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const status = getGatewayStatus(config, secrets);\n if (!status.configured) {\n console.log(status.message);\n console.log(\"本地 Web UI 仍会启动,方便继续配置。\");\n await startWebServer(config);\n return;\n }\n\n const database = openDatabase(config);\n const vectorStore = hasEmbeddingConfig(config, secrets) ? new SQLiteVectorStore(database) : null;\n const gatewayRuntime = createFeishuGateway({\n config,\n secrets,\n ingestor: new GatewayIngestor(database),\n resourceDownloader: FeishuResourceDownloader.fromConfig(config, secrets),\n attachmentVectorIndexer: vectorStore\n ? (messageId) =>\n indexMessageChunks({\n messages: new MessageRepository(database),\n embedding: createEmbeddingModel(config, secrets),\n store: vectorStore,\n messageIds: [messageId],\n })\n : undefined,\n questionHandler: new FeishuQuestionHandler({\n config,\n secrets,\n database,\n sender: FeishuMessageSender.fromConfig(config, secrets),\n model: createChatModel(config, secrets),\n }),\n });\n\n const cleanup = () => {\n gatewayRuntime.stop();\n vectorStore?.close();\n database.close();\n removeGatewayPidRecord();\n };\n\n process.on(\"SIGINT\", () => {\n cleanup();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n cleanup();\n process.exit(0);\n });\n\n console.log(status.message);\n writeGatewayPidRecord();\n\n try {\n await gatewayRuntime.start();\n await startWebServer(config);\n } catch (error) {\n cleanup();\n throw error;\n }\n}\n\ngateway.command(\"status\").description(\"查看 Gateway 状态\").action(async () => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n console.log(JSON.stringify(getGatewayStatus(config, secrets), null, 2));\n});\n\ngateway.command(\"start\").description(\"启动飞书长连接 Gateway 和本地 Web UI\").action(startGatewayCommand);\n\ngateway.command(\"stop\").description(\"停止 Gateway\").action(() => {\n console.log(stopGatewayProcess().message);\n});\n\ngateway.command(\"restart\").description(\"重启 Gateway\").action(async () => {\n console.log(stopGatewayProcess().message);\n await startGatewayCommand();\n});\n\nconst web = program.command(\"web\").description(\"管理本地 Web UI\");\n\nweb.command(\"start\").description(\"启动本地 Web UI\").action(async () => {\n const config = await loadConfig();\n await startWebServer(config);\n});\n\nconst data = program.command(\"data\").description(\"管理本地知识库数据\");\n\nasync function deleteDataCommand(targetType: DeleteTargetType, targetId: string, options: { yes?: boolean }): Promise<void> {\n const shouldDelete =\n options.yes ||\n (await confirm({\n message: `确认删除 ${targetType}=${targetId} 的本地知识库记录?`,\n default: false,\n }));\n\n if (!shouldDelete) {\n console.log(\"已取消。\");\n return;\n }\n\n const config = await loadConfig();\n const database = openDatabase(config);\n try {\n const result = await deleteLocalData({ config, database, targetType, targetId });\n console.log(\n `删除完成:messages=${result.deletedMessages},chunks=${result.deletedChunks},fileJobs=${result.deletedFileJobs},chats=${result.deletedChats}`,\n );\n if (result.deletedStoredFiles.length > 0) {\n console.log(`已删除本地保存文件:${result.deletedStoredFiles.join(\";\")}`);\n }\n if (result.skippedStoredFiles.length > 0) {\n console.log(`跳过非数据目录文件:${result.skippedStoredFiles.join(\";\")}`);\n }\n console.log(\"SQLite FTS 已同步删除;如使用语义检索,请运行 chattercatcher index rebuild 重建 SQLite Vector。\");\n } finally {\n database.close();\n }\n}\n\nconst dataDelete = data.command(\"delete\").description(\"删除指定本地知识库数据\");\n\ndataDelete\n .command(\"message\")\n .description(\"按消息 ID 删除一条消息及其 RAG chunks\")\n .argument(\"<messageId>\", \"消息 ID\")\n .option(\"--yes\", \"跳过确认\")\n .action((messageId: string, options: { yes?: boolean }) => deleteDataCommand(\"message\", messageId, options));\n\ndataDelete\n .command(\"file\")\n .description(\"按文件消息 ID 删除文件知识源、解析任务和 dataDir 内保存文件\")\n .argument(\"<messageId>\", \"文件消息 ID\")\n .option(\"--yes\", \"跳过确认\")\n .action((messageId: string, options: { yes?: boolean }) => deleteDataCommand(\"file\", messageId, options));\n\ndataDelete\n .command(\"chat\")\n .description(\"按群聊 ID 删除该群聊下的消息和 RAG chunks\")\n .argument(\"<chatId>\", \"群聊 ID\")\n .option(\"--yes\", \"跳过确认\")\n .action((chatId: string, options: { yes?: boolean }) => deleteDataCommand(\"chat\", chatId, options));\n\nconst index = program.command(\"index\").description(\"管理 RAG 索引\");\n\nindex.command(\"status\").description(\"查看索引状态\").action(async () => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const vectorStore = new SQLiteVectorStore(database);\n const vectors = await vectorStore.count();\n console.log(JSON.stringify(\n {\n database: getDatabasePath(config),\n vectorDatabase: getDatabasePath(config),\n chats: messages.getChatCount(),\n messages: messages.getMessageCount(),\n vectors,\n retrieval: {\n keyword: \"SQLite FTS5\",\n vector: hasEmbeddingConfig(config, secrets) ? \"SQLite Vector 已可用于语义检索\" : \"SQLite Vector 已接入;需配置 embedding 后启用语义检索\",\n hybrid: \"启用:SQLite FTS + SQLite Vector\",\n rag: \"强制先检索证据再回答,禁止全量上下文堆叠\",\n },\n },\n null,\n 2,\n ));\n vectorStore.close();\n database.close();\n});\n\nindex.command(\"rebuild\").description(\"重建 SQLite 向量索引\").option(\"--limit <number>\", \"最多索引的 chunk 数\", \"10000\").action(async (options: { limit: string }) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n\n if (!hasEmbeddingConfig(config, secrets)) {\n console.log(\"Embedding 配置不完整,无法重建向量索引。请运行 chattercatcher setup 或 chattercatcher settings。\");\n return;\n }\n\n const database = openDatabase(config);\n const vectorStore = new SQLiteVectorStore(database);\n\n try {\n const stats = await indexMessageChunks({\n messages: new MessageRepository(database),\n embedding: createEmbeddingModel(config, secrets),\n store: vectorStore,\n limit: Number(options.limit),\n });\n console.log(`向量索引完成:chunks=${stats.chunks}, vectors=${stats.vectors}`);\n } finally {\n vectorStore.close();\n database.close();\n }\n});\n\nconst processCommand = program.command(\"process\").description(\"立即处理后台任务\");\n\nprocessCommand\n .command(\"messages\")\n .description(\"立即处理消息索引任务,把消息 chunks 写入 SQLite 向量索引\")\n .option(\"--limit <number>\", \"最多处理的 chunk 数\", \"10000\")\n .action(async (options: { limit: string }) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n const limit = Number(options.limit);\n\n try {\n const result = await processMessagesNow({\n config,\n secrets,\n database,\n limit: Number.isFinite(limit) ? limit : 10000,\n });\n if (result.status === \"skipped\") {\n console.log(`处理跳过:${result.reason}`);\n return;\n }\n\n console.log(`消息处理完成:chunks=${result.chunks}, vectors=${result.vectors}`);\n } finally {\n database.close();\n }\n });\n\nconst files = program.command(\"files\").description(\"管理本地文件知识源\");\n\nfiles\n .command(\"add\")\n .description(\"把本地文件解析、保存到数据目录并写入 RAG 知识库\")\n .argument(\"<paths...>\", \"文件路径,支持 txt、md、json、csv、tsv、log、docx、pdf\")\n .action(async (paths: string[]) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const jobs = new FileJobRepository(database);\n\n try {\n for (const filePath of paths) {\n const result = await ingestLocalFile({ config, messages, jobs, filePath });\n console.log(\n `已导入文件:${result.fileName},解析器=${result.parser},字符数=${result.characters},消息ID=${result.messageId}`,\n );\n }\n console.log(\"文件已进入 SQLite FTS 检索;如已配置 embedding,可运行 chattercatcher index rebuild 更新 SQLite 向量索引。\");\n } finally {\n database.close();\n }\n });\n\nfiles\n .command(\"jobs\")\n .description(\"查看文件解析任务状态\")\n .option(\"--limit <number>\", \"最多显示的任务数\", \"50\")\n .option(\"--status <status>\", \"按状态过滤:processing、indexed、failed\")\n .action(async (options: { limit: string; status?: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const limit = Number(options.limit);\n\n try {\n const status =\n options.status === \"processing\" || options.status === \"indexed\" || options.status === \"failed\"\n ? options.status\n : undefined;\n const jobs = new FileJobRepository(database).list(Number.isFinite(limit) ? limit : 50, { status });\n if (jobs.length === 0) {\n console.log(\"还没有文件解析任务。\");\n return;\n }\n\n for (const job of jobs) {\n console.log(\n `${job.fileName} | ID=${job.id} | 状态=${job.status} | 解析器=${job.parser ?? \"-\"} | 更新时间=${job.updatedAt}`,\n );\n if (job.error) {\n console.log(` 错误:${job.error}`);\n }\n if (job.storedPath) {\n console.log(` 本地保存:${job.storedPath}`);\n }\n }\n } finally {\n database.close();\n }\n });\n\nfiles\n .command(\"retry\")\n .description(\"重试一个失败的文件解析任务\")\n .argument(\"<jobId>\", \"文件解析任务 ID\")\n .action(async (jobId: string) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const jobs = new FileJobRepository(database);\n const messages = new MessageRepository(database);\n\n try {\n const job = jobs.get(jobId);\n if (!job) {\n console.log(`没有找到文件解析任务:${jobId}`);\n return;\n }\n\n const result = await ingestLocalFile({\n config,\n messages,\n jobs,\n filePath: job.sourcePath,\n });\n console.log(\n `重试完成:${result.fileName},状态=indexed,解析器=${result.parser},消息ID=${result.messageId}`,\n );\n } finally {\n database.close();\n }\n });\n\nfiles\n .command(\"list\")\n .description(\"查看已进入 RAG 的本地文件\")\n .option(\"--limit <number>\", \"最多显示的文件数\", \"50\")\n .action(async (options: { limit: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const limit = Number(options.limit);\n\n try {\n const files = messages.listFiles(Number.isFinite(limit) ? limit : 50);\n if (files.length === 0) {\n console.log(\"还没有文件。可运行 chattercatcher files add <path...> 导入文件。\");\n return;\n }\n\n for (const file of files) {\n console.log(\n `${file.fileName} | 解析器=${file.parser ?? \"unknown\"} | 字符数=${file.characters} | 导入时间=${file.importedAt}`,\n );\n if (file.parserWarnings?.length) {\n console.log(` 解析警告:${file.parserWarnings.join(\";\")}`);\n }\n if (file.storedPath) {\n console.log(` 本地保存:${file.storedPath}`);\n }\n }\n } finally {\n database.close();\n }\n });\n\nprogram.command(\"logs\").description(\"查看本地日志\").option(\"--follow\", \"持续输出日志\").option(\"--lines <number>\", \"显示末尾行数\", \"200\").option(\"--file <name>\", \"指定日志文件名或绝对路径\").action(async (options: { follow?: boolean; lines?: string; file?: string }) => {\n const result = await readLatestLogTail({\n fileName: options.file,\n lines: normalizeLineCount(options.lines),\n });\n\n if (!result) {\n console.log(`还没有日志文件:${getLogsDirectory()}`);\n return;\n }\n\n console.log(`日志文件:${result.file.path}`);\n if (result.content) {\n console.log(result.content);\n }\n\n if (!options.follow) {\n return;\n }\n\n const stop = await followLogFile({\n filePath: result.file.path,\n onChunk: (chunk) => process.stdout.write(chunk),\n onError: (error) => console.error(`日志跟随失败:${error.message}`),\n });\n\n const shutdown = () => {\n stop();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n await new Promise(() => undefined);\n});\n\nprogram.command(\"export\").description(\"导出本地知识库数据(不包含密钥)\").option(\"--out <path>\", \"导出 JSON 文件路径\").action(async (options: { out?: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n\n try {\n const result = await exportLocalData({ config, database, outputPath: options.out });\n console.log(`导出完成:${result.outputPath}`);\n console.log(`包含:群聊=${result.chats},消息=${result.messages},chunks=${result.chunks},文件任务=${result.fileJobs}`);\n } finally {\n database.close();\n }\n});\n\nprogram.command(\"restore\").description(\"从 ChatterCatcher 导出文件恢复本地知识库数据\").argument(\"<file>\", \"导出的 JSON 文件\").option(\"--replace\", \"先清空当前本地知识库,再恢复\").action(async (file: string, options: { replace?: boolean }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n\n try {\n const result = await restoreLocalData({ database, inputPath: file, replace: options.replace });\n console.log(`恢复完成:${result.inputPath}`);\n console.log(`模式:${result.mode === \"replace\" ? \"替换\" : \"合并\"}`);\n console.log(`包含:群聊=${result.chats},消息=${result.messages},chunks=${result.chunks},文件任务=${result.fileJobs}`);\n console.log(\"SQLite FTS 已重建;如使用语义检索,请运行 chattercatcher index rebuild 重建 SQLite Vector。\");\n } finally {\n database.close();\n }\n});\n\nconst dev = program.command(\"dev\").description(\"开发调试命令\");\n\ndev\n .command(\"ingest-message\")\n .description(\"写入一条本地测试消息\")\n .requiredOption(\"--text <text>\", \"消息文本\")\n .option(\"--chat <name>\", \"群名\", \"家庭群\")\n .option(\"--sender <name>\", \"发送人\", \"测试用户\")\n .action(async (options: { text: string; chat: string; sender: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const now = new Date().toISOString();\n const id = messages.ingest({\n platform: \"dev\",\n platformChatId: options.chat,\n chatName: options.chat,\n platformMessageId: `dev-${Date.now()}`,\n senderId: options.sender,\n senderName: options.sender,\n messageType: \"text\",\n text: options.text,\n sentAt: now,\n rawPayload: { dev: true },\n });\n\n console.log(`已写入消息:${id}`);\n database.close();\n });\n\ndev\n .command(\"ingest-feishu-event\")\n .description(\"从 JSON 文件模拟写入一条飞书消息事件\")\n .requiredOption(\"--file <path>\", \"飞书事件 JSON 文件\")\n .action(async (options: { file: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n\n try {\n const raw = await fs.readFile(options.file, \"utf8\");\n const payload = JSON.parse(raw) as FeishuReceiveMessageEvent;\n const result = new GatewayIngestor(database).ingestFeishuEvent(payload);\n\n if (!result.accepted) {\n console.log(result.reason);\n return;\n }\n\n console.log(`已写入飞书消息:${result.messageId}`);\n } finally {\n database.close();\n }\n });\n\ndev\n .command(\"search\")\n .description(\"通过本地 FTS 检索测试 RAG 证据\")\n .argument(\"<question>\", \"检索问题\")\n .action(async (question: string) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n const { retriever, close } = await createHybridRetriever({\n config,\n secrets,\n messages: new MessageRepository(database),\n });\n const evidence = await retriever.retrieve(question);\n\n if (evidence.length === 0) {\n console.log(\"没有检索到证据。\");\n close();\n database.close();\n return;\n }\n\n console.log(JSON.stringify(evidence, null, 2));\n close();\n database.close();\n });\n\ndev\n .command(\"ask\")\n .description(\"通过本地检索证据调用 LLM 回答\")\n .argument(\"<question>\", \"问题\")\n .action(async (question: string) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n const { retriever, close } = await createHybridRetriever({\n config,\n secrets,\n messages: new MessageRepository(database),\n });\n\n try {\n const result = await askWithRag({\n question,\n retriever,\n model: createChatModel(config, secrets),\n });\n\n console.log(result.answer);\n if (result.citations.length > 0) {\n console.log(\"\\n引用:\");\n for (const citation of result.citations) {\n console.log(`- ${formatCitation(citation)}`);\n }\n }\n } finally {\n close();\n database.close();\n }\n });\n\nprogram.parseAsync().catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n});\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n type AppConfig,\n type AppSecrets,\n appConfigSchema,\n appSecretsSchema,\n createDefaultConfig,\n createDefaultSecrets,\n} from \"./schema.js\";\nimport { getChatterCatcherHome, getConfigPath, getSecretsPath } from \"./paths.js\";\n\nasync function readJsonFile<T>(filePath: string, fallback: T): Promise<T> {\n try {\n const raw = await fs.readFile(filePath, \"utf8\");\n return JSON.parse(raw) as T;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return fallback;\n }\n\n throw error;\n }\n}\n\nasync function writeJsonFile(filePath: string, value: unknown): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nexport async function loadConfig(): Promise<AppConfig> {\n const raw = await readJsonFile(getConfigPath(), createDefaultConfig());\n return appConfigSchema.parse(raw);\n}\n\nexport async function saveConfig(config: AppConfig): Promise<void> {\n await writeJsonFile(getConfigPath(), appConfigSchema.parse(config));\n}\n\nexport async function loadSecrets(): Promise<AppSecrets> {\n const raw = await readJsonFile(getSecretsPath(), createDefaultSecrets());\n return appSecretsSchema.parse(raw);\n}\n\nexport async function saveSecrets(secrets: AppSecrets): Promise<void> {\n await writeJsonFile(getSecretsPath(), appSecretsSchema.parse(secrets));\n}\n\nexport async function ensureConfigFiles(): Promise<{ config: AppConfig; secrets: AppSecrets }> {\n await fs.mkdir(getChatterCatcherHome(), { recursive: true });\n const config = await loadConfig();\n const secrets = await loadSecrets();\n await saveConfig(config);\n await saveSecrets(secrets);\n return { config, secrets };\n}\n\nexport async function resetConfigFiles(): Promise<void> {\n await saveConfig(createDefaultConfig());\n await saveSecrets(createDefaultSecrets());\n}\n\nexport function maskSecret(value: string): string {\n if (!value) {\n return \"\";\n }\n\n if (value.length <= 8) {\n return \"********\";\n }\n\n return `${value.slice(0, 4)}...${value.slice(-4)}`;\n}\n","import os from \"node:os\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\n\nfunction defaultDataDir(): string {\n return path.join(process.env.CHATTERCATCHER_HOME || path.join(os.homedir(), \".chattercatcher\"), \"data\");\n}\n\nexport const appConfigSchema = z.object({\n feishu: z.object({\n domain: z.enum([\"feishu\", \"lark\"]).default(\"feishu\"),\n appId: z.string().default(\"\"),\n groupPolicy: z.enum([\"open\", \"allowlist\", \"disabled\"]).default(\"open\"),\n requireMention: z.boolean().default(true),\n }),\n llm: z.object({\n baseUrl: z.string().url().or(z.literal(\"\")).default(\"\"),\n model: z.string().default(\"\"),\n }),\n embedding: z.object({\n baseUrl: z.string().url().or(z.literal(\"\")).default(\"\"),\n model: z.string().default(\"\"),\n dimension: z.number().int().positive().nullable().default(null),\n }),\n storage: z.object({\n dataDir: z.string().default(defaultDataDir),\n }),\n web: z.object({\n host: z.string().default(\"127.0.0.1\"),\n port: z.number().int().min(1).max(65535).default(3878),\n }),\n schedules: z.object({\n indexing: z.string().default(\"*/10 * * * *\"),\n }),\n});\n\nexport const appSecretsSchema = z.object({\n feishu: z.object({\n appSecret: z.string().default(\"\"),\n }),\n llm: z.object({\n apiKey: z.string().default(\"\"),\n }),\n embedding: z.object({\n apiKey: z.string().default(\"\"),\n }),\n});\n\nexport type AppConfig = z.infer<typeof appConfigSchema>;\nexport type AppSecrets = z.infer<typeof appSecretsSchema>;\n\nexport function createDefaultConfig(): AppConfig {\n return appConfigSchema.parse({\n feishu: {},\n llm: {},\n embedding: {},\n storage: {},\n web: {},\n schedules: {},\n });\n}\n\nexport function createDefaultSecrets(): AppSecrets {\n return appSecretsSchema.parse({\n feishu: {},\n llm: {},\n embedding: {},\n });\n}\n","import os from \"node:os\";\nimport path from \"node:path\";\n\nexport function getChatterCatcherHome(): string {\n return process.env.CHATTERCATCHER_HOME || path.join(os.homedir(), \".chattercatcher\");\n}\n\nexport function resolveHomePath(value: string): string {\n if (value === \"~\") {\n return os.homedir();\n }\n\n if (value.startsWith(\"~/\") || value.startsWith(\"~\\\\\")) {\n return path.join(os.homedir(), value.slice(2));\n }\n\n return path.resolve(value);\n}\n\nexport function getConfigPath(): string {\n return path.join(getChatterCatcherHome(), \"config.json\");\n}\n\nexport function getSecretsPath(): string {\n return path.join(getChatterCatcherHome(), \"secrets.json\");\n}\n","export function applySecretInput(currentValue: string, nextValue: string | undefined): string {\n const trimmed = nextValue?.trim() ?? \"\";\n return trimmed ? trimmed : currentValue;\n}\n\nexport function resolveEmbeddingApiKey(input: {\n currentEmbeddingKey: string;\n nextEmbeddingKey?: string;\n llmApiKey: string;\n}): string {\n const explicit = applySecretInput(input.currentEmbeddingKey, input.nextEmbeddingKey);\n return explicit || input.llmApiKey;\n}\n\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport type DeleteTargetType = \"message\" | \"chat\" | \"file\";\n\nexport interface DeleteLocalDataResult {\n targetType: DeleteTargetType;\n targetId: string;\n deletedMessages: number;\n deletedChunks: number;\n deletedFileJobs: number;\n deletedChats: number;\n deletedStoredFiles: string[];\n skippedStoredFiles: string[];\n}\n\ninterface StoredPathRow {\n storedPath: string | null;\n}\n\nfunction emptyResult(targetType: DeleteTargetType, targetId: string): DeleteLocalDataResult {\n return {\n targetType,\n targetId,\n deletedMessages: 0,\n deletedChunks: 0,\n deletedFileJobs: 0,\n deletedChats: 0,\n deletedStoredFiles: [],\n skippedStoredFiles: [],\n };\n}\n\nfunction parseStoredPathFromRawPayload(rawPayloadJson: string): string | null {\n try {\n const parsed = JSON.parse(rawPayloadJson) as unknown;\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return null;\n }\n\n const storedPath = (parsed as { storedPath?: unknown }).storedPath;\n return typeof storedPath === \"string\" ? storedPath : null;\n } catch {\n return null;\n }\n}\n\nfunction isInsideDirectory(filePath: string, directory: string): boolean {\n const relative = path.relative(path.resolve(directory), path.resolve(filePath));\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n\nasync function removeStoredFiles(config: AppConfig, paths: string[]): Promise<{\n deleted: string[];\n skipped: string[];\n}> {\n const dataDir = resolveHomePath(config.storage.dataDir);\n const deleted: string[] = [];\n const skipped: string[] = [];\n const uniquePaths = [...new Set(paths.filter(Boolean).map((item) => path.resolve(item)))];\n\n for (const storedPath of uniquePaths) {\n if (!isInsideDirectory(storedPath, dataDir)) {\n skipped.push(storedPath);\n continue;\n }\n\n try {\n await fs.rm(storedPath, { force: true });\n deleted.push(storedPath);\n } catch {\n skipped.push(storedPath);\n }\n }\n\n return { deleted, skipped };\n}\n\nfunction getStoredPathsForMessages(database: SqliteDatabase, messageIds: string[]): string[] {\n if (messageIds.length === 0) {\n return [];\n }\n\n const rows = database\n .prepare(\n `\n SELECT raw_payload_json AS rawPayloadJson\n FROM messages\n WHERE id IN (${messageIds.map(() => \"?\").join(\", \")})\n `,\n )\n .all(...messageIds) as Array<{ rawPayloadJson: string }>;\n\n const fileJobRows = database\n .prepare(\n `\n SELECT stored_path AS storedPath\n FROM file_jobs\n WHERE message_id IN (${messageIds.map(() => \"?\").join(\", \")})\n `,\n )\n .all(...messageIds) as StoredPathRow[];\n\n return [\n ...rows.map((row) => parseStoredPathFromRawPayload(row.rawPayloadJson)).filter((item): item is string => Boolean(item)),\n ...fileJobRows.map((row) => row.storedPath).filter((item): item is string => Boolean(item)),\n ];\n}\n\nfunction deleteMessagesByIds(database: SqliteDatabase, messageIds: string[]): Omit<\n DeleteLocalDataResult,\n \"targetType\" | \"targetId\" | \"deletedStoredFiles\" | \"skippedStoredFiles\" | \"deletedChats\"\n> {\n if (messageIds.length === 0) {\n return {\n deletedMessages: 0,\n deletedChunks: 0,\n deletedFileJobs: 0,\n };\n }\n\n const placeholders = messageIds.map(() => \"?\").join(\", \");\n const deletedChunks = (database.prepare(`SELECT COUNT(*) AS count FROM message_chunks WHERE message_id IN (${placeholders})`).get(...messageIds) as { count: number }).count;\n const deletedFileJobs = database.prepare(`DELETE FROM file_jobs WHERE message_id IN (${placeholders})`).run(...messageIds).changes;\n database.prepare(`DELETE FROM message_chunks_fts WHERE message_id IN (${placeholders})`).run(...messageIds);\n const deletedMessages = database.prepare(`DELETE FROM messages WHERE id IN (${placeholders})`).run(...messageIds).changes;\n\n return {\n deletedMessages,\n deletedChunks,\n deletedFileJobs,\n };\n}\n\nexport async function deleteLocalData(input: {\n config: AppConfig;\n database: SqliteDatabase;\n targetType: DeleteTargetType;\n targetId: string;\n}): Promise<DeleteLocalDataResult> {\n const result = emptyResult(input.targetType, input.targetId);\n let storedPaths: string[] = [];\n\n const transaction = input.database.transaction(() => {\n if (input.targetType === \"chat\") {\n const messageIds = (\n input.database.prepare(\"SELECT id FROM messages WHERE chat_id = ?\").all(input.targetId) as Array<{ id: string }>\n ).map((row) => row.id);\n storedPaths = getStoredPathsForMessages(input.database, messageIds);\n const deleted = deleteMessagesByIds(input.database, messageIds);\n result.deletedMessages = deleted.deletedMessages;\n result.deletedChunks = deleted.deletedChunks;\n result.deletedFileJobs = deleted.deletedFileJobs;\n result.deletedChats = input.database.prepare(\"DELETE FROM chats WHERE id = ?\").run(input.targetId).changes;\n return;\n }\n\n if (input.targetType === \"file\") {\n const file = input.database\n .prepare(\"SELECT id FROM messages WHERE id = ? AND message_type = 'file'\")\n .get(input.targetId) as { id: string } | undefined;\n if (!file) {\n return;\n }\n }\n\n storedPaths = getStoredPathsForMessages(input.database, [input.targetId]);\n const deleted = deleteMessagesByIds(input.database, [input.targetId]);\n result.deletedMessages = deleted.deletedMessages;\n result.deletedChunks = deleted.deletedChunks;\n result.deletedFileJobs = deleted.deletedFileJobs;\n });\n\n transaction();\n\n const removed = await removeStoredFiles(input.config, storedPaths);\n result.deletedStoredFiles = removed.deleted;\n result.skippedStoredFiles = removed.skipped;\n return result;\n}\n","import Database from \"better-sqlite3\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\n\nexport type SqliteDatabase = Database.Database;\n\nexport function getDatabasePath(config: AppConfig): string {\n return path.join(resolveHomePath(config.storage.dataDir), \"chattercatcher.db\");\n}\n\nexport function openDatabase(config: AppConfig): SqliteDatabase {\n const databasePath = getDatabasePath(config);\n fs.mkdirSync(path.dirname(databasePath), { recursive: true });\n\n const database = new Database(databasePath);\n database.pragma(\"journal_mode = WAL\");\n database.pragma(\"foreign_keys = ON\");\n migrateDatabase(database);\n return database;\n}\n\nexport function migrateDatabase(database: SqliteDatabase): void {\n database.exec(`\n CREATE TABLE IF NOT EXISTS chats (\n id TEXT PRIMARY KEY,\n platform TEXT NOT NULL,\n platform_chat_id TEXT NOT NULL,\n name TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE(platform, platform_chat_id)\n );\n\n CREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n platform TEXT NOT NULL,\n platform_message_id TEXT NOT NULL,\n chat_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,\n sender_id TEXT NOT NULL,\n sender_name TEXT NOT NULL,\n message_type TEXT NOT NULL,\n text TEXT NOT NULL,\n raw_payload_json TEXT NOT NULL,\n sent_at TEXT NOT NULL,\n received_at TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE(platform, platform_message_id)\n );\n\n CREATE TABLE IF NOT EXISTS message_chunks (\n id TEXT PRIMARY KEY,\n message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n chunk_index INTEGER NOT NULL,\n text TEXT NOT NULL,\n metadata_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE(message_id, chunk_index)\n );\n\n CREATE VIRTUAL TABLE IF NOT EXISTS message_chunks_fts USING fts5(\n text,\n chunk_id UNINDEXED,\n message_id UNINDEXED,\n tokenize = 'unicode61'\n );\n\n CREATE TABLE IF NOT EXISTS message_chunk_vectors (\n chunk_id TEXT PRIMARY KEY REFERENCES message_chunks(id) ON DELETE CASCADE,\n vector_json TEXT NOT NULL,\n evidence_json TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS file_jobs (\n id TEXT PRIMARY KEY,\n source_path TEXT NOT NULL,\n stored_path TEXT,\n file_name TEXT NOT NULL,\n status TEXT NOT NULL,\n parser TEXT,\n message_id TEXT,\n bytes INTEGER,\n characters INTEGER,\n warnings_json TEXT NOT NULL DEFAULT '[]',\n error TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n `);\n}\n","import fs from \"node:fs/promises\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport { getChatterCatcherHome } from \"../config/paths.js\";\nimport { getDatabasePath, openDatabase } from \"../db/database.js\";\nimport { FileJobRepository } from \"../files/jobs.js\";\nimport { getGatewayStatus } from \"../gateway/index.js\";\nimport { createChatModel, createEmbeddingModel } from \"../llm/openai-compatible.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { hasEmbeddingConfig } from \"../rag/factory.js\";\nimport { SQLiteVectorStore } from \"../rag/sqlite-vector-store.js\";\n\nexport type DoctorStatus = \"pass\" | \"warn\" | \"fail\";\n\nexport interface DoctorCheck {\n name: string;\n status: DoctorStatus;\n message: string;\n}\n\nexport interface DoctorOptions {\n online?: boolean;\n}\n\nfunction pass(name: string, message: string): DoctorCheck {\n return { name, status: \"pass\", message };\n}\n\nfunction warn(name: string, message: string): DoctorCheck {\n return { name, status: \"warn\", message };\n}\n\nfunction fail(name: string, message: string): DoctorCheck {\n return { name, status: \"fail\", message };\n}\n\nexport async function runDoctor(\n config: AppConfig,\n secrets: AppSecrets,\n options: DoctorOptions = {},\n): Promise<DoctorCheck[]> {\n const checks: DoctorCheck[] = [];\n\n checks.push(await checkHomeDirectory());\n checks.push(checkFeishu(config, secrets));\n checks.push(checkLlmConfig(config, secrets));\n checks.push(checkEmbeddingConfig(config, secrets));\n checks.push(await checkSqlite(config));\n checks.push(await checkFilePipeline(config));\n checks.push(await checkSqliteVector(config));\n checks.push(checkRagPolicy());\n\n if (options.online) {\n checks.push(await checkChatModel(config, secrets));\n checks.push(await checkEmbeddingModel(config, secrets));\n }\n\n return checks;\n}\n\nasync function checkHomeDirectory(): Promise<DoctorCheck> {\n const home = getChatterCatcherHome();\n try {\n await fs.mkdir(home, { recursive: true });\n await fs.access(home);\n return pass(\"配置目录\", home);\n } catch (error) {\n return fail(\"配置目录\", error instanceof Error ? error.message : String(error));\n }\n}\n\nfunction checkFeishu(config: AppConfig, secrets: AppSecrets): DoctorCheck {\n const status = getGatewayStatus(config, secrets);\n if (status.configured) {\n return pass(\"飞书 Gateway\", status.message);\n }\n\n return warn(\"飞书 Gateway\", status.message);\n}\n\nfunction checkLlmConfig(config: AppConfig, secrets: AppSecrets): DoctorCheck {\n if (!config.llm.baseUrl || !config.llm.model || !secrets.llm.apiKey) {\n return warn(\"LLM 配置\", \"未配置完整;@ 提问时无法生成模型回答。\");\n }\n\n return pass(\"LLM 配置\", `${config.llm.model} @ ${config.llm.baseUrl}`);\n}\n\nfunction checkEmbeddingConfig(config: AppConfig, secrets: AppSecrets): DoctorCheck {\n if (!hasEmbeddingConfig(config, secrets)) {\n return warn(\"Embedding 配置\", \"未配置完整;RAG 会使用 SQLite FTS,无法使用 SQLite Vector 语义检索。\");\n }\n\n return pass(\"Embedding 配置\", `${config.embedding.model} @ ${config.embedding.baseUrl || config.llm.baseUrl}`);\n}\n\nasync function checkSqlite(config: AppConfig): Promise<DoctorCheck> {\n let database: ReturnType<typeof openDatabase> | null = null;\n try {\n database = openDatabase(config);\n const messages = new MessageRepository(database);\n return pass(\"SQLite\", `${getDatabasePath(config)};messages=${messages.getMessageCount()}`);\n } catch (error) {\n return fail(\"SQLite\", error instanceof Error ? error.message : String(error));\n } finally {\n database?.close();\n }\n}\n\nasync function checkFilePipeline(config: AppConfig): Promise<DoctorCheck> {\n let database: ReturnType<typeof openDatabase> | null = null;\n try {\n database = openDatabase(config);\n const messages = new MessageRepository(database);\n const jobs = new FileJobRepository(database);\n const fileCount = messages.listFiles(1_000_000).length;\n const failedJobs = jobs.list(1_000_000, { status: \"failed\" });\n\n if (failedJobs.length > 0) {\n return warn(\"文件解析\", `files=${fileCount};failed_jobs=${failedJobs.length};可运行 chattercatcher files jobs --status failed 查看。`);\n }\n\n return pass(\"文件解析\", `files=${fileCount};failed_jobs=0`);\n } catch (error) {\n return fail(\"文件解析\", error instanceof Error ? error.message : String(error));\n } finally {\n database?.close();\n }\n}\n\nasync function checkSqliteVector(config: AppConfig): Promise<DoctorCheck> {\n let database: ReturnType<typeof openDatabase> | null = null;\n try {\n database = openDatabase(config);\n const store = new SQLiteVectorStore(database);\n const count = await store.count();\n return pass(\"SQLite Vector\", `${getDatabasePath(config)};vectors=${count}`);\n } catch (error) {\n return fail(\"SQLite Vector\", error instanceof Error ? error.message : String(error));\n } finally {\n database?.close();\n }\n}\n\nfunction checkRagPolicy(): DoctorCheck {\n return pass(\"RAG 策略\", \"强制先检索证据再回答;禁止全量上下文堆叠。\");\n}\n\nasync function checkChatModel(config: AppConfig, secrets: AppSecrets): Promise<DoctorCheck> {\n if (!config.llm.baseUrl || !config.llm.model || !secrets.llm.apiKey) {\n return warn(\"LLM 连通性\", \"跳过:LLM 配置不完整。\");\n }\n\n try {\n const answer = await createChatModel(config, secrets).complete([{ role: \"user\", content: \"Reply with OK only.\" }]);\n return pass(\"LLM 连通性\", answer.slice(0, 80));\n } catch (error) {\n return fail(\"LLM 连通性\", error instanceof Error ? error.message : String(error));\n }\n}\n\nasync function checkEmbeddingModel(config: AppConfig, secrets: AppSecrets): Promise<DoctorCheck> {\n if (!hasEmbeddingConfig(config, secrets)) {\n return warn(\"Embedding 连通性\", \"跳过:Embedding 配置不完整。\");\n }\n\n try {\n const vector = await createEmbeddingModel(config, secrets).embed(\"chattercatcher doctor\");\n if (vector.length === 0) {\n return fail(\"Embedding 连通性\", \"返回向量为空。\");\n }\n\n return pass(\"Embedding 连通性\", `dimension=${vector.length}`);\n } catch (error) {\n return fail(\"Embedding 连通性\", error instanceof Error ? error.message : String(error));\n }\n}\n\nexport function formatDoctorChecks(checks: DoctorCheck[]): string {\n const icon: Record<DoctorStatus, string> = {\n pass: \"PASS\",\n warn: \"WARN\",\n fail: \"FAIL\",\n };\n\n return checks.map((check) => `[${icon[check.status]}] ${check.name}: ${check.message}`).join(\"\\n\");\n}\n","import crypto from \"node:crypto\";\nimport path from \"node:path\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport type FileJobStatus = \"processing\" | \"indexed\" | \"failed\";\n\nexport interface FileJobRecord {\n id: string;\n sourcePath: string;\n storedPath?: string;\n fileName: string;\n status: FileJobStatus;\n parser?: string;\n messageId?: string;\n bytes?: number;\n characters?: number;\n warnings: string[];\n error?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction stableJobId(sourcePath: string): string {\n return crypto.createHash(\"sha256\").update(path.resolve(sourcePath)).digest(\"hex\").slice(0, 32);\n}\n\nfunction parseWarnings(value: string): string[] {\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === \"string\") : [];\n } catch {\n return [];\n }\n}\n\nexport class FileJobRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n start(input: { sourcePath: string; fileName?: string }): string {\n const id = stableJobId(input.sourcePath);\n const now = nowIso();\n this.database\n .prepare(\n `\n INSERT INTO file_jobs (\n id, source_path, file_name, status, warnings_json, created_at, updated_at\n )\n VALUES (@id, @sourcePath, @fileName, 'processing', '[]', @createdAt, @updatedAt)\n ON CONFLICT(id) DO UPDATE SET\n source_path = excluded.source_path,\n file_name = excluded.file_name,\n status = 'processing',\n parser = NULL,\n message_id = NULL,\n bytes = NULL,\n characters = NULL,\n warnings_json = '[]',\n error = NULL,\n updated_at = excluded.updated_at\n `,\n )\n .run({\n id,\n sourcePath: path.resolve(input.sourcePath),\n fileName: input.fileName ?? path.basename(input.sourcePath),\n createdAt: now,\n updatedAt: now,\n });\n return id;\n }\n\n complete(input: {\n id: string;\n storedPath: string;\n parser: string;\n messageId: string;\n bytes: number;\n characters: number;\n warnings: string[];\n }): void {\n this.database\n .prepare(\n `\n UPDATE file_jobs\n SET\n stored_path = @storedPath,\n status = 'indexed',\n parser = @parser,\n message_id = @messageId,\n bytes = @bytes,\n characters = @characters,\n warnings_json = @warningsJson,\n error = NULL,\n updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({\n id: input.id,\n storedPath: input.storedPath,\n parser: input.parser,\n messageId: input.messageId,\n bytes: input.bytes,\n characters: input.characters,\n warningsJson: JSON.stringify(input.warnings),\n updatedAt: nowIso(),\n });\n }\n\n fail(input: { id: string; error: string }): void {\n this.database\n .prepare(\n `\n UPDATE file_jobs\n SET status = 'failed', error = @error, updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({\n id: input.id,\n error: input.error,\n updatedAt: nowIso(),\n });\n }\n\n get(id: string): FileJobRecord | null {\n return this.listByWhere(\"WHERE id = ?\", [id], 1)[0] ?? null;\n }\n\n list(limit = 50, options: { status?: FileJobStatus } = {}): FileJobRecord[] {\n return options.status ? this.listByWhere(\"WHERE status = ?\", [options.status], limit) : this.listByWhere(\"\", [], limit);\n }\n\n private listByWhere(whereSql: string, params: unknown[], limit: number): FileJobRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n source_path AS sourcePath,\n stored_path AS storedPath,\n file_name AS fileName,\n status,\n parser,\n message_id AS messageId,\n bytes,\n characters,\n warnings_json AS warningsJson,\n error,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM file_jobs\n ${whereSql}\n ORDER BY updated_at DESC\n LIMIT ?\n `,\n )\n .all(...params, limit) as Array<{\n id: string;\n sourcePath: string;\n storedPath: string | null;\n fileName: string;\n status: FileJobStatus;\n parser: string | null;\n messageId: string | null;\n bytes: number | null;\n characters: number | null;\n warningsJson: string;\n error: string | null;\n createdAt: string;\n updatedAt: string;\n }>;\n\n return rows.map((row) => ({\n id: row.id,\n sourcePath: row.sourcePath,\n storedPath: row.storedPath ?? undefined,\n fileName: row.fileName,\n status: row.status,\n parser: row.parser ?? undefined,\n messageId: row.messageId ?? undefined,\n bytes: row.bytes ?? undefined,\n characters: row.characters ?? undefined,\n warnings: parseWarnings(row.warningsJson),\n error: row.error ?? undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n }));\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getChatterCatcherHome } from \"../config/paths.js\";\n\nexport interface GatewayPidRecord {\n pid: number;\n startedAt: string;\n command: string;\n}\n\nexport interface GatewayRuntimeState {\n pidFile: string;\n record: GatewayPidRecord | null;\n running: boolean;\n stale: boolean;\n}\n\nexport interface StopGatewayResult {\n stopped: boolean;\n message: string;\n}\n\nexport function getGatewayPidPath(): string {\n return path.join(getChatterCatcherHome(), \"gateway.pid\");\n}\n\nexport function isProcessRunning(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) {\n return false;\n }\n\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function readGatewayPidRecord(pidFile = getGatewayPidPath()): GatewayPidRecord | null {\n try {\n const raw = fs.readFileSync(pidFile, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<GatewayPidRecord>;\n if (!Number.isInteger(parsed.pid) || typeof parsed.startedAt !== \"string\" || typeof parsed.command !== \"string\") {\n return null;\n }\n\n const pid = parsed.pid;\n if (pid === undefined) {\n return null;\n }\n\n return {\n pid,\n startedAt: parsed.startedAt,\n command: parsed.command,\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n\n return null;\n }\n}\n\nexport function writeGatewayPidRecord(pidFile = getGatewayPidPath(), record: GatewayPidRecord = {\n pid: process.pid,\n startedAt: new Date().toISOString(),\n command: process.argv.join(\" \"),\n}): void {\n fs.mkdirSync(path.dirname(pidFile), { recursive: true });\n fs.writeFileSync(pidFile, `${JSON.stringify(record, null, 2)}\\n`, \"utf8\");\n}\n\nexport function removeGatewayPidRecord(pidFile = getGatewayPidPath()): void {\n try {\n fs.rmSync(pidFile, { force: true });\n } catch {\n // Best-effort cleanup only.\n }\n}\n\nexport function getGatewayRuntimeState(pidFile = getGatewayPidPath()): GatewayRuntimeState {\n const record = readGatewayPidRecord(pidFile);\n const running = record ? isProcessRunning(record.pid) : false;\n return {\n pidFile,\n record,\n running,\n stale: Boolean(record && !running),\n };\n}\n\nexport function stopGatewayProcess(pidFile = getGatewayPidPath()): StopGatewayResult {\n const state = getGatewayRuntimeState(pidFile);\n if (!state.record) {\n return {\n stopped: false,\n message: \"Gateway 没有运行记录。\",\n };\n }\n\n if (state.stale) {\n removeGatewayPidRecord(pidFile);\n return {\n stopped: false,\n message: `Gateway PID 文件已过期,已清理:${state.record.pid}`,\n };\n }\n\n if (state.record.pid === process.pid) {\n return {\n stopped: false,\n message: \"拒绝停止当前 CLI 进程;请在另一个终端运行 stop,或按 Ctrl+C。\",\n };\n }\n\n try {\n process.kill(state.record.pid, \"SIGTERM\");\n removeGatewayPidRecord(pidFile);\n return {\n stopped: true,\n message: `已向 Gateway 进程发送停止信号:pid=${state.record.pid}`,\n };\n } catch (error) {\n return {\n stopped: false,\n message: `停止 Gateway 失败:${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n","import type { AppConfig } from \"../config/schema.js\";\nimport type { AppSecrets } from \"../config/schema.js\";\nimport { getGatewayRuntimeState } from \"./runtime.js\";\n\nexport interface GatewayStatus {\n configured: boolean;\n connection: \"not_configured\" | \"ready_for_start\" | \"running\";\n message: string;\n pid?: number;\n pidFile?: string;\n}\n\nexport function getGatewayStatus(config: AppConfig, secrets?: AppSecrets): GatewayStatus {\n if (!config.feishu.appId) {\n return {\n configured: false,\n connection: \"not_configured\",\n message: \"尚未配置飞书 App ID。请运行 chattercatcher setup 或 chattercatcher settings。\",\n };\n }\n\n if (secrets && !secrets.feishu.appSecret) {\n return {\n configured: false,\n connection: \"not_configured\",\n message: \"尚未配置飞书 App Secret。请运行 chattercatcher setup 或 chattercatcher settings。\",\n };\n }\n\n const runtime = getGatewayRuntimeState();\n if (runtime.running && runtime.record) {\n return {\n configured: true,\n connection: \"running\",\n message: `飞书 Gateway 正在运行:pid=${runtime.record.pid},startedAt=${runtime.record.startedAt}`,\n pid: runtime.record.pid,\n pidFile: runtime.pidFile,\n };\n }\n\n if (runtime.stale && runtime.record) {\n return {\n configured: true,\n connection: \"ready_for_start\",\n message: `飞书长连接配置已就绪;发现过期 PID 文件:pid=${runtime.record.pid}。运行 chattercatcher gateway start 会覆盖运行记录。`,\n pid: runtime.record.pid,\n pidFile: runtime.pidFile,\n };\n }\n\n return {\n configured: true,\n connection: \"ready_for_start\",\n message: \"飞书长连接配置已就绪。运行 chattercatcher gateway start 后会接收 im.message.receive_v1 事件。\",\n pidFile: runtime.pidFile,\n };\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { EmbeddingModel } from \"../rag/embedding.js\";\nimport type { ChatMessage, ChatModel } from \"../rag/types.js\";\n\nexport interface OpenAICompatibleChatOptions {\n baseUrl: string;\n apiKey: string;\n model: string;\n temperature?: number;\n}\n\ninterface ChatCompletionResponse {\n choices?: Array<{\n message?: {\n content?: string;\n };\n }>;\n}\n\ninterface EmbeddingResponse {\n data?: Array<{\n embedding?: number[];\n }>;\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\");\n}\n\nexport class OpenAICompatibleChatModel implements ChatModel {\n constructor(private readonly options: OpenAICompatibleChatOptions) {}\n\n async complete(messages: ChatMessage[]): Promise<string> {\n if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {\n throw new Error(\"LLM 配置不完整。请运行 chattercatcher setup 或 chattercatcher settings。\");\n }\n\n const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/chat/completions`, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${this.options.apiKey}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n model: this.options.model,\n messages,\n temperature: this.options.temperature ?? 0.2,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`LLM 请求失败:${response.status} ${body}`);\n }\n\n const data = (await response.json()) as ChatCompletionResponse;\n const content = data.choices?.[0]?.message?.content?.trim();\n if (!content) {\n throw new Error(\"LLM 返回为空。\");\n }\n\n return content;\n }\n}\n\nexport interface OpenAICompatibleEmbeddingOptions {\n baseUrl: string;\n apiKey: string;\n model: string;\n}\n\nexport class OpenAICompatibleEmbeddingModel implements EmbeddingModel {\n constructor(private readonly options: OpenAICompatibleEmbeddingOptions) {}\n\n async embed(text: string): Promise<number[]> {\n const [vector] = await this.embedBatch([text]);\n return vector ?? [];\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {\n throw new Error(\"Embedding 配置不完整。请运行 chattercatcher setup 或 chattercatcher settings。\");\n }\n\n const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/embeddings`, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${this.options.apiKey}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n model: this.options.model,\n input: texts,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Embedding 请求失败:${response.status} ${body}`);\n }\n\n const data = (await response.json()) as EmbeddingResponse;\n return data.data?.map((item) => item.embedding ?? []) ?? [];\n }\n}\n\nexport function createChatModel(config: AppConfig, secrets: AppSecrets): OpenAICompatibleChatModel {\n return new OpenAICompatibleChatModel({\n baseUrl: config.llm.baseUrl,\n apiKey: secrets.llm.apiKey,\n model: config.llm.model,\n });\n}\n\nexport function createEmbeddingModel(config: AppConfig, secrets: AppSecrets): OpenAICompatibleEmbeddingModel {\n return new OpenAICompatibleEmbeddingModel({\n baseUrl: config.embedding.baseUrl || config.llm.baseUrl,\n apiKey: secrets.embedding.apiKey || secrets.llm.apiKey,\n model: config.embedding.model,\n });\n}\n","import crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { chunkText } from \"./chunker.js\";\nimport type { ChatRecord, FileRecord, IngestMessageInput, MessageSearchResult } from \"./types.js\";\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction stableId(parts: string[]): string {\n return crypto.createHash(\"sha256\").update(parts.join(\"\\u001f\")).digest(\"hex\").slice(0, 32);\n}\n\nfunction escapeFtsQuery(query: string): string {\n const terms = query\n .trim()\n .split(/\\s+/)\n .map((term) => term.replace(/\"/g, \"\\\"\\\"\"))\n .filter(Boolean);\n\n if (terms.length === 0) {\n return \"\\\"\\\"\";\n }\n\n return terms.map((term) => `\"${term}\"`).join(\" OR \");\n}\n\nfunction escapeLikeTerm(term: string): string {\n return term.replace(/[\\\\%_]/g, (match) => `\\\\${match}`);\n}\n\nfunction buildSearchTerms(query: string): string[] {\n const trimmed = query.trim();\n if (!trimmed) {\n return [];\n }\n\n const terms = trimmed.split(/\\s+/).filter(Boolean);\n if (terms.length > 1) {\n return terms;\n }\n\n if (/[\\u3400-\\u9fff]/.test(trimmed) && trimmed.length > 2) {\n const cjkTerms = new Set<string>([trimmed]);\n for (let index = 0; index < trimmed.length - 1; index += 1) {\n cjkTerms.add(trimmed.slice(index, index + 2));\n }\n\n return [...cjkTerms];\n }\n\n return [trimmed];\n}\n\nfunction parseRawPayload(value: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nexport class MessageRepository {\n constructor(readonly database: SqliteDatabase) {}\n\n ingest(input: IngestMessageInput): string {\n const createdAt = nowIso();\n const chatId = stableId([input.platform, input.platformChatId]);\n const messageId = stableId([input.platform, input.platformMessageId]);\n const rawPayloadJson = JSON.stringify(input.rawPayload ?? {});\n const chunks = chunkText(input.text);\n\n const transaction = this.database.transaction(() => {\n this.database\n .prepare(\n `\n INSERT INTO chats (id, platform, platform_chat_id, name, created_at, updated_at)\n VALUES (@id, @platform, @platformChatId, @name, @createdAt, @updatedAt)\n ON CONFLICT(platform, platform_chat_id)\n DO UPDATE SET name = excluded.name, updated_at = excluded.updated_at\n `,\n )\n .run({\n id: chatId,\n platform: input.platform,\n platformChatId: input.platformChatId,\n name: input.chatName,\n createdAt,\n updatedAt: createdAt,\n });\n\n this.database\n .prepare(\n `\n INSERT INTO messages (\n id, platform, platform_message_id, chat_id, sender_id, sender_name,\n message_type, text, raw_payload_json, sent_at, received_at, created_at\n )\n VALUES (\n @id, @platform, @platformMessageId, @chatId, @senderId, @senderName,\n @messageType, @text, @rawPayloadJson, @sentAt, @receivedAt, @createdAt\n )\n ON CONFLICT(platform, platform_message_id)\n DO UPDATE SET\n text = excluded.text,\n raw_payload_json = excluded.raw_payload_json,\n received_at = excluded.received_at\n `,\n )\n .run({\n id: messageId,\n platform: input.platform,\n platformMessageId: input.platformMessageId,\n chatId,\n senderId: input.senderId,\n senderName: input.senderName,\n messageType: input.messageType,\n text: input.text,\n rawPayloadJson,\n sentAt: input.sentAt,\n receivedAt: createdAt,\n createdAt,\n });\n\n this.database.prepare(\"DELETE FROM message_chunks_fts WHERE message_id = ?\").run(messageId);\n this.database.prepare(\"DELETE FROM message_chunks WHERE message_id = ?\").run(messageId);\n\n const insertChunk = this.database.prepare(`\n INSERT INTO message_chunks (id, message_id, chunk_index, text, metadata_json, created_at)\n VALUES (@id, @messageId, @chunkIndex, @text, @metadataJson, @createdAt)\n `);\n const insertFts = this.database.prepare(`\n INSERT INTO message_chunks_fts (text, chunk_id, message_id)\n VALUES (@text, @chunkId, @messageId)\n `);\n\n for (const chunk of chunks) {\n const chunkId = stableId([messageId, String(chunk.index)]);\n insertChunk.run({\n id: chunkId,\n messageId,\n chunkIndex: chunk.index,\n text: chunk.text,\n metadataJson: JSON.stringify({ sourceType: \"message\" }),\n createdAt,\n });\n insertFts.run({ text: chunk.text, chunkId, messageId });\n }\n });\n\n transaction();\n return messageId;\n }\n\n listRecentMessages(limit = 20): MessageSearchResult[] {\n return this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n m.id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n 1.0 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks mc\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n ORDER BY m.sent_at DESC\n LIMIT ?\n `,\n )\n .all(limit) as MessageSearchResult[];\n }\n\n listAllMessageChunks(limit = 10000): MessageSearchResult[] {\n return this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n m.id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n 1.0 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks mc\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n ORDER BY m.sent_at DESC, mc.chunk_index ASC\n LIMIT ?\n `,\n )\n .all(limit) as MessageSearchResult[];\n }\n\n listMessageChunksByMessageIds(messageIds: string[], limit = 10000): MessageSearchResult[] {\n if (messageIds.length === 0) {\n return [];\n }\n\n return this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n m.id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n 1.0 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks mc\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n WHERE m.id IN (${messageIds.map(() => \"?\").join(\", \")})\n ORDER BY m.sent_at DESC, mc.chunk_index ASC\n LIMIT ?\n `,\n )\n .all(...messageIds, limit) as MessageSearchResult[];\n }\n\n searchMessages(query: string, limit = 8, options: { excludeMessageIds?: string[] } = {}): MessageSearchResult[] {\n const ftsQuery = escapeFtsQuery(query);\n const excludedIds = options.excludeMessageIds ?? [];\n const excludedWhere = excludedIds.length > 0 ? `AND fts.message_id NOT IN (${excludedIds.map(() => \"?\").join(\", \")})` : \"\";\n const ftsResults = this.database\n .prepare(\n `\n SELECT\n fts.chunk_id AS chunkId,\n fts.message_id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n bm25(message_chunks_fts) * -1 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks_fts fts\n JOIN message_chunks mc ON mc.id = fts.chunk_id\n JOIN messages m ON m.id = fts.message_id\n JOIN chats c ON c.id = m.chat_id\n WHERE message_chunks_fts MATCH ?\n ${excludedWhere}\n ORDER BY bm25(message_chunks_fts)\n LIMIT ?\n `,\n )\n .all(ftsQuery, ...excludedIds, limit) as MessageSearchResult[];\n\n if (ftsResults.length > 0) {\n return ftsResults;\n }\n\n const terms = buildSearchTerms(query);\n if (terms.length === 0) {\n return [];\n }\n\n const where = terms.map(() => \"mc.text LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n const params = terms.map((term) => `%${escapeLikeTerm(term)}%`);\n const likeExcludedWhere =\n excludedIds.length > 0 ? `AND m.id NOT IN (${excludedIds.map(() => \"?\").join(\", \")})` : \"\";\n\n return this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n m.id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n 0.1 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks mc\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n WHERE (${where})\n ${likeExcludedWhere}\n ORDER BY m.sent_at DESC\n LIMIT ?\n `,\n )\n .all(...params, ...excludedIds, limit) as MessageSearchResult[];\n }\n\n getChatCount(): number {\n return (this.database.prepare(\"SELECT COUNT(*) AS count FROM chats\").get() as { count: number }).count;\n }\n\n getMessageCount(): number {\n return (this.database.prepare(\"SELECT COUNT(*) AS count FROM messages\").get() as { count: number }).count;\n }\n\n hasPlatformMessage(platform: string, platformMessageId: string): boolean {\n const row = this.database\n .prepare(\"SELECT 1 AS existsFlag FROM messages WHERE platform = ? AND platform_message_id = ? LIMIT 1\")\n .get(platform, platformMessageId) as { existsFlag: number } | undefined;\n return Boolean(row);\n }\n\n listChats(): ChatRecord[] {\n return this.database\n .prepare(\n `\n SELECT\n id,\n platform,\n platform_chat_id AS platformChatId,\n name,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM chats\n ORDER BY updated_at DESC\n `,\n )\n .all() as ChatRecord[];\n }\n\n listFiles(limit = 50): FileRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id AS messageId,\n sender_name AS fileName,\n raw_payload_json AS rawPayloadJson,\n length(text) AS characters,\n created_at AS importedAt\n FROM messages\n WHERE message_type = 'file'\n ORDER BY created_at DESC\n LIMIT ?\n `,\n )\n .all(limit) as Array<{\n messageId: string;\n fileName: string;\n rawPayloadJson: string;\n characters: number;\n importedAt: string;\n }>;\n\n return rows.map((row) => {\n const payload = parseRawPayload(row.rawPayloadJson);\n return {\n messageId: row.messageId,\n fileName: row.fileName,\n sourcePath: typeof payload.sourcePath === \"string\" ? payload.sourcePath : undefined,\n storedPath: typeof payload.storedPath === \"string\" ? payload.storedPath : undefined,\n bytes: typeof payload.bytes === \"number\" ? payload.bytes : undefined,\n characters: row.characters,\n parser: typeof payload.parser === \"string\" ? payload.parser : undefined,\n parserWarnings: Array.isArray(payload.parserWarnings)\n ? payload.parserWarnings.filter((item): item is string => typeof item === \"string\")\n : undefined,\n importedAt: row.importedAt,\n };\n });\n }\n}\n","export interface TextChunk {\n index: number;\n text: string;\n}\n\nexport function chunkText(text: string, maxChars = 900, overlapChars = 120): TextChunk[] {\n const normalized = text.trim().replace(/\\s+/g, \" \");\n if (!normalized) {\n return [];\n }\n\n if (normalized.length <= maxChars) {\n return [{ index: 0, text: normalized }];\n }\n\n const chunks: TextChunk[] = [];\n let cursor = 0;\n\n while (cursor < normalized.length) {\n const end = Math.min(cursor + maxChars, normalized.length);\n chunks.push({ index: chunks.length, text: normalized.slice(cursor, end) });\n\n if (end === normalized.length) {\n break;\n }\n\n cursor = Math.max(end - overlapChars, cursor + 1);\n }\n\n return chunks;\n}\n\n","import type { Retriever } from \"./retriever.js\";\nimport type { EvidenceBlock } from \"./types.js\";\n\nexport interface HybridRetrieverOptions {\n limit?: number;\n}\n\nfunction normalizeScore(score: number): number {\n if (!Number.isFinite(score)) {\n return 0;\n }\n\n return Math.max(0, Math.min(1, score));\n}\n\nexport class HybridRetriever implements Retriever {\n constructor(\n private readonly retrievers: Retriever[],\n private readonly options: HybridRetrieverOptions = {},\n ) {}\n\n async retrieve(question: string): Promise<EvidenceBlock[]> {\n const results = await Promise.all(this.retrievers.map((retriever) => retriever.retrieve(question)));\n const merged = new Map<string, EvidenceBlock>();\n\n for (const [retrieverIndex, evidenceList] of results.entries()) {\n for (const evidence of evidenceList) {\n const existing = merged.get(evidence.id);\n const weightedScore = normalizeScore(evidence.score) + (this.retrievers.length - retrieverIndex) * 0.01;\n\n if (!existing || weightedScore > existing.score) {\n merged.set(evidence.id, {\n ...evidence,\n score: weightedScore,\n });\n }\n }\n }\n\n return [...merged.values()]\n .sort((left, right) => right.score - left.score)\n .slice(0, this.options.limit ?? 8);\n }\n}\n\n","import { MessageRepository } from \"../messages/repository.js\";\nimport type { MessageSearchResult } from \"../messages/types.js\";\nimport type { EvidenceBlock } from \"./types.js\";\nimport type { Retriever } from \"./retriever.js\";\n\nfunction toEvidenceSource(result: MessageSearchResult): EvidenceBlock[\"source\"] {\n if (result.messageType === \"file\") {\n return {\n type: \"file\",\n label: result.senderName,\n timestamp: result.sentAt,\n };\n }\n\n return {\n type: \"message\",\n label: result.chatName,\n sender: result.senderName,\n timestamp: result.sentAt,\n };\n}\n\nexport class MessageFtsRetriever implements Retriever {\n constructor(\n private readonly messages: MessageRepository,\n private readonly options: { excludeMessageIds?: string[] } = {},\n ) {}\n\n async retrieve(question: string): Promise<EvidenceBlock[]> {\n const results = this.messages.searchMessages(question, 8, {\n excludeMessageIds: this.options.excludeMessageIds,\n });\n\n return results.map((result) => ({\n id: result.chunkId,\n text: result.text,\n score: result.score,\n source: toEvidenceSource(result),\n }));\n }\n}\n","export interface EmbeddingModel {\n embed(text: string): Promise<number[]>;\n embedBatch(texts: string[]): Promise<number[][]>;\n}\n\nexport function cosineSimilarity(left: number[], right: number[]): number {\n if (left.length === 0 || right.length === 0 || left.length !== right.length) {\n return 0;\n }\n\n let dot = 0;\n let leftNorm = 0;\n let rightNorm = 0;\n\n for (let index = 0; index < left.length; index += 1) {\n const leftValue = left[index] ?? 0;\n const rightValue = right[index] ?? 0;\n dot += leftValue * rightValue;\n leftNorm += leftValue * leftValue;\n rightNorm += rightValue * rightValue;\n }\n\n if (leftNorm === 0 || rightNorm === 0) {\n return 0;\n }\n\n return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));\n}\n\n","import type { SqliteDatabase } from \"../db/database.js\";\nimport { cosineSimilarity } from \"./embedding.js\";\nimport type { EvidenceBlock } from \"./types.js\";\nimport type { VectorRecord, VectorSearchResult, VectorStore } from \"./vector-store.js\";\n\ninterface VectorRow {\n chunk_id: string;\n vector_json: string;\n evidence_json: string;\n}\n\nexport class SQLiteVectorStore implements VectorStore {\n constructor(private readonly database: SqliteDatabase) {}\n\n async upsert(records: VectorRecord[]): Promise<void> {\n if (records.length === 0) {\n return;\n }\n\n const statement = this.database.prepare(`\n INSERT INTO message_chunk_vectors (chunk_id, vector_json, evidence_json, updated_at)\n VALUES (@chunkId, @vectorJson, @evidenceJson, @updatedAt)\n ON CONFLICT(chunk_id) DO UPDATE SET\n vector_json = excluded.vector_json,\n evidence_json = excluded.evidence_json,\n updated_at = excluded.updated_at\n `);\n const upsertMany = this.database.transaction((items: VectorRecord[]) => {\n const updatedAt = new Date().toISOString();\n for (const item of items) {\n statement.run({\n chunkId: item.id,\n vectorJson: JSON.stringify(item.vector),\n evidenceJson: JSON.stringify(item.evidence),\n updatedAt,\n });\n }\n });\n\n upsertMany(records);\n }\n\n async search(vector: number[], limit: number): Promise<VectorSearchResult[]> {\n if (limit <= 0) {\n return [];\n }\n\n const rows = this.database\n .prepare(\"SELECT chunk_id, vector_json, evidence_json FROM message_chunk_vectors\")\n .all() as VectorRow[];\n\n return rows\n .map((row) => {\n const storedVector = JSON.parse(row.vector_json) as number[];\n const evidence = JSON.parse(row.evidence_json) as EvidenceBlock;\n const vectorScore = cosineSimilarity(vector, storedVector);\n return {\n ...evidence,\n score: vectorScore,\n vectorScore,\n };\n })\n .sort((left, right) => right.vectorScore - left.vectorScore)\n .slice(0, limit);\n }\n\n async count(): Promise<number> {\n const row = this.database.prepare(\"SELECT COUNT(*) AS count FROM message_chunk_vectors\").get() as { count: number };\n return row.count;\n }\n\n close(): void {\n // The SQLite database lifecycle is owned by the caller.\n }\n}\n","import type { EmbeddingModel } from \"./embedding.js\";\nimport type { Retriever } from \"./retriever.js\";\nimport type { EvidenceBlock } from \"./types.js\";\nimport type { VectorStore } from \"./vector-store.js\";\n\nexport class VectorRetriever implements Retriever {\n constructor(\n private readonly embedding: EmbeddingModel,\n private readonly store: VectorStore,\n private readonly limit = 8,\n ) {}\n\n async retrieve(question: string): Promise<EvidenceBlock[]> {\n const vector = await this.embedding.embed(question);\n return this.store.search(vector, this.limit);\n }\n}\n\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport { createEmbeddingModel } from \"../llm/openai-compatible.js\";\nimport { HybridRetriever } from \"./hybrid-retriever.js\";\nimport { MessageFtsRetriever } from \"./message-retriever.js\";\nimport type { Retriever } from \"./retriever.js\";\nimport { SQLiteVectorStore } from \"./sqlite-vector-store.js\";\nimport { VectorRetriever } from \"./vector-retriever.js\";\n\nexport function hasEmbeddingConfig(config: AppConfig, secrets: AppSecrets): boolean {\n return Boolean((config.embedding.baseUrl || config.llm.baseUrl) && config.embedding.model && (secrets.embedding.apiKey || secrets.llm.apiKey));\n}\n\nexport async function createHybridRetriever(input: {\n config: AppConfig;\n secrets: AppSecrets;\n messages: MessageRepository;\n excludeMessageIds?: string[];\n}): Promise<{ retriever: Retriever; close: () => void }> {\n const retrievers: Retriever[] = [new MessageFtsRetriever(input.messages, { excludeMessageIds: input.excludeMessageIds })];\n const closers: Array<() => void> = [];\n\n if (hasEmbeddingConfig(input.config, input.secrets)) {\n const vectorStore = new SQLiteVectorStore(input.messages.database);\n retrievers.push(new VectorRetriever(createEmbeddingModel(input.config, input.secrets), vectorStore));\n closers.push(() => vectorStore.close());\n }\n\n return {\n retriever: new HybridRetriever(retrievers),\n close: () => {\n for (const closer of closers) {\n closer();\n }\n },\n };\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport interface DataExportResult {\n outputPath: string;\n chats: number;\n messages: number;\n chunks: number;\n fileJobs: number;\n}\n\nfunction parseJsonObject(value: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nfunction parseJsonArray(value: string): unknown[] {\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n}\n\nfunction defaultExportPath(config: AppConfig, exportedAt: string): string {\n const fileName = `chattercatcher-export-${exportedAt.replace(/[:.]/g, \"-\")}.json`;\n return path.join(resolveHomePath(config.storage.dataDir), \"exports\", fileName);\n}\n\nexport async function exportLocalData(input: {\n config: AppConfig;\n database: SqliteDatabase;\n outputPath?: string;\n exportedAt?: string;\n}): Promise<DataExportResult> {\n const exportedAt = input.exportedAt ?? new Date().toISOString();\n const outputPath = path.resolve(input.outputPath ?? defaultExportPath(input.config, exportedAt));\n\n const chats = input.database\n .prepare(\n `\n SELECT\n id,\n platform,\n platform_chat_id AS platformChatId,\n name,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM chats\n ORDER BY updated_at ASC\n `,\n )\n .all();\n\n const messages = (\n input.database\n .prepare(\n `\n SELECT\n id,\n platform,\n platform_message_id AS platformMessageId,\n chat_id AS chatId,\n sender_id AS senderId,\n sender_name AS senderName,\n message_type AS messageType,\n text,\n raw_payload_json AS rawPayloadJson,\n sent_at AS sentAt,\n received_at AS receivedAt,\n created_at AS createdAt\n FROM messages\n ORDER BY sent_at ASC, created_at ASC\n `,\n )\n .all() as Array<Record<string, unknown> & { rawPayloadJson: string }>\n ).map(({ rawPayloadJson, ...message }) => ({\n ...message,\n rawPayload: parseJsonObject(rawPayloadJson),\n }));\n\n const chunks = (\n input.database\n .prepare(\n `\n SELECT\n id,\n message_id AS messageId,\n chunk_index AS chunkIndex,\n text,\n metadata_json AS metadataJson,\n created_at AS createdAt\n FROM message_chunks\n ORDER BY message_id ASC, chunk_index ASC\n `,\n )\n .all() as Array<Record<string, unknown> & { metadataJson: string }>\n ).map(({ metadataJson, ...chunk }) => ({\n ...chunk,\n metadata: parseJsonObject(metadataJson),\n }));\n\n const fileJobs = (\n input.database\n .prepare(\n `\n SELECT\n id,\n source_path AS sourcePath,\n stored_path AS storedPath,\n file_name AS fileName,\n status,\n parser,\n message_id AS messageId,\n bytes,\n characters,\n warnings_json AS warningsJson,\n error,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM file_jobs\n ORDER BY updated_at ASC\n `,\n )\n .all() as Array<Record<string, unknown> & { warningsJson: string }>\n ).map(({ warningsJson, ...job }) => ({\n ...job,\n warnings: parseJsonArray(warningsJson).filter((item): item is string => typeof item === \"string\"),\n }));\n\n const payload = {\n app: \"ChatterCatcher\",\n schemaVersion: 1,\n exportedAt,\n note: \"本文件只包含本地知识库数据,不包含 API Key、App Secret 或 token。\",\n data: {\n chats,\n messages,\n chunks,\n fileJobs,\n },\n };\n\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}\\n`, \"utf8\");\n\n return {\n outputPath,\n chats: chats.length,\n messages: messages.length,\n chunks: chunks.length,\n fileJobs: fileJobs.length,\n };\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport interface DataRestoreResult {\n inputPath: string;\n mode: \"merge\" | \"replace\";\n chats: number;\n messages: number;\n chunks: number;\n fileJobs: number;\n}\n\ninterface ExportPayload {\n app: string;\n schemaVersion: number;\n data: {\n chats: Array<Record<string, unknown>>;\n messages: Array<Record<string, unknown>>;\n chunks: Array<Record<string, unknown>>;\n fileJobs: Array<Record<string, unknown>>;\n };\n}\n\nfunction asObject(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};\n}\n\nfunction asArray(value: unknown): Array<Record<string, unknown>> {\n return Array.isArray(value) ? value.map(asObject) : [];\n}\n\nfunction asString(value: unknown, field: string): string {\n if (typeof value !== \"string\") {\n throw new Error(`恢复文件字段无效:${field}`);\n }\n\n return value;\n}\n\nfunction asOptionalString(value: unknown): string | null {\n return typeof value === \"string\" ? value : null;\n}\n\nfunction asOptionalNumber(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction asJson(value: unknown, fallback: unknown): string {\n return JSON.stringify(value === undefined ? fallback : value);\n}\n\nfunction parsePayload(raw: string): ExportPayload {\n const parsed = asObject(JSON.parse(raw) as unknown);\n const data = asObject(parsed.data);\n const payload = {\n app: asString(parsed.app, \"app\"),\n schemaVersion: typeof parsed.schemaVersion === \"number\" ? parsed.schemaVersion : NaN,\n data: {\n chats: asArray(data.chats),\n messages: asArray(data.messages),\n chunks: asArray(data.chunks),\n fileJobs: asArray(data.fileJobs),\n },\n };\n\n if (payload.app !== \"ChatterCatcher\" || payload.schemaVersion !== 1) {\n throw new Error(\"恢复文件不是 ChatterCatcher schemaVersion=1 导出。\");\n }\n\n return payload;\n}\n\nfunction clearDatabase(database: SqliteDatabase): void {\n database.prepare(\"DELETE FROM message_chunks_fts\").run();\n database.prepare(\"DELETE FROM message_chunks\").run();\n database.prepare(\"DELETE FROM file_jobs\").run();\n database.prepare(\"DELETE FROM messages\").run();\n database.prepare(\"DELETE FROM chats\").run();\n}\n\nexport async function restoreLocalData(input: {\n database: SqliteDatabase;\n inputPath: string;\n replace?: boolean;\n}): Promise<DataRestoreResult> {\n const inputPath = path.resolve(input.inputPath);\n const payload = parsePayload(await fs.readFile(inputPath, \"utf8\"));\n const mode = input.replace ? \"replace\" : \"merge\";\n\n const restore = input.database.transaction(() => {\n if (input.replace) {\n clearDatabase(input.database);\n }\n\n const upsertChat = input.database.prepare(`\n INSERT INTO chats (id, platform, platform_chat_id, name, created_at, updated_at)\n VALUES (@id, @platform, @platformChatId, @name, @createdAt, @updatedAt)\n ON CONFLICT(id) DO UPDATE SET\n platform = excluded.platform,\n platform_chat_id = excluded.platform_chat_id,\n name = excluded.name,\n updated_at = excluded.updated_at\n `);\n\n const upsertMessage = input.database.prepare(`\n INSERT INTO messages (\n id, platform, platform_message_id, chat_id, sender_id, sender_name,\n message_type, text, raw_payload_json, sent_at, received_at, created_at\n )\n VALUES (\n @id, @platform, @platformMessageId, @chatId, @senderId, @senderName,\n @messageType, @text, @rawPayloadJson, @sentAt, @receivedAt, @createdAt\n )\n ON CONFLICT(id) DO UPDATE SET\n platform = excluded.platform,\n platform_message_id = excluded.platform_message_id,\n chat_id = excluded.chat_id,\n sender_id = excluded.sender_id,\n sender_name = excluded.sender_name,\n message_type = excluded.message_type,\n text = excluded.text,\n raw_payload_json = excluded.raw_payload_json,\n sent_at = excluded.sent_at,\n received_at = excluded.received_at\n `);\n\n const upsertChunk = input.database.prepare(`\n INSERT INTO message_chunks (id, message_id, chunk_index, text, metadata_json, created_at)\n VALUES (@id, @messageId, @chunkIndex, @text, @metadataJson, @createdAt)\n ON CONFLICT(id) DO UPDATE SET\n message_id = excluded.message_id,\n chunk_index = excluded.chunk_index,\n text = excluded.text,\n metadata_json = excluded.metadata_json\n `);\n\n const insertFts = input.database.prepare(`\n INSERT INTO message_chunks_fts (text, chunk_id, message_id)\n VALUES (@text, @chunkId, @messageId)\n `);\n\n const upsertFileJob = input.database.prepare(`\n INSERT INTO file_jobs (\n id, source_path, stored_path, file_name, status, parser, message_id,\n bytes, characters, warnings_json, error, created_at, updated_at\n )\n VALUES (\n @id, @sourcePath, @storedPath, @fileName, @status, @parser, @messageId,\n @bytes, @characters, @warningsJson, @error, @createdAt, @updatedAt\n )\n ON CONFLICT(id) DO UPDATE SET\n source_path = excluded.source_path,\n stored_path = excluded.stored_path,\n file_name = excluded.file_name,\n status = excluded.status,\n parser = excluded.parser,\n message_id = excluded.message_id,\n bytes = excluded.bytes,\n characters = excluded.characters,\n warnings_json = excluded.warnings_json,\n error = excluded.error,\n updated_at = excluded.updated_at\n `);\n\n for (const chat of payload.data.chats) {\n upsertChat.run({\n id: asString(chat.id, \"chat.id\"),\n platform: asString(chat.platform, \"chat.platform\"),\n platformChatId: asString(chat.platformChatId, \"chat.platformChatId\"),\n name: asString(chat.name, \"chat.name\"),\n createdAt: asString(chat.createdAt, \"chat.createdAt\"),\n updatedAt: asString(chat.updatedAt, \"chat.updatedAt\"),\n });\n }\n\n for (const message of payload.data.messages) {\n upsertMessage.run({\n id: asString(message.id, \"message.id\"),\n platform: asString(message.platform, \"message.platform\"),\n platformMessageId: asString(message.platformMessageId, \"message.platformMessageId\"),\n chatId: asString(message.chatId, \"message.chatId\"),\n senderId: asString(message.senderId, \"message.senderId\"),\n senderName: asString(message.senderName, \"message.senderName\"),\n messageType: asString(message.messageType, \"message.messageType\"),\n text: asString(message.text, \"message.text\"),\n rawPayloadJson: asJson(message.rawPayload, {}),\n sentAt: asString(message.sentAt, \"message.sentAt\"),\n receivedAt: asString(message.receivedAt, \"message.receivedAt\"),\n createdAt: asString(message.createdAt, \"message.createdAt\"),\n });\n input.database.prepare(\"DELETE FROM message_chunks_fts WHERE message_id = ?\").run(asString(message.id, \"message.id\"));\n }\n\n for (const chunk of payload.data.chunks) {\n const messageId = asString(chunk.messageId, \"chunk.messageId\");\n const chunkId = asString(chunk.id, \"chunk.id\");\n const text = asString(chunk.text, \"chunk.text\");\n upsertChunk.run({\n id: chunkId,\n messageId,\n chunkIndex: asOptionalNumber(chunk.chunkIndex) ?? 0,\n text,\n metadataJson: asJson(chunk.metadata, {}),\n createdAt: asString(chunk.createdAt, \"chunk.createdAt\"),\n });\n insertFts.run({ text, chunkId, messageId });\n }\n\n for (const job of payload.data.fileJobs) {\n upsertFileJob.run({\n id: asString(job.id, \"fileJob.id\"),\n sourcePath: asString(job.sourcePath, \"fileJob.sourcePath\"),\n storedPath: asOptionalString(job.storedPath),\n fileName: asString(job.fileName, \"fileJob.fileName\"),\n status: asString(job.status, \"fileJob.status\"),\n parser: asOptionalString(job.parser),\n messageId: asOptionalString(job.messageId),\n bytes: asOptionalNumber(job.bytes),\n characters: asOptionalNumber(job.characters),\n warningsJson: asJson(Array.isArray(job.warnings) ? job.warnings : [], []),\n error: asOptionalString(job.error),\n createdAt: asString(job.createdAt, \"fileJob.createdAt\"),\n updatedAt: asString(job.updatedAt, \"fileJob.updatedAt\"),\n });\n }\n });\n\n restore();\n\n return {\n inputPath,\n mode,\n chats: payload.data.chats.length,\n messages: payload.data.messages.length,\n chunks: payload.data.chunks.length,\n fileJobs: payload.data.fileJobs.length,\n };\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { GatewayIngestAndDownloadResult, GatewayIngestor } from \"../gateway/ingest.js\";\nimport type { FeishuReceiveMessageEvent } from \"./normalize.js\";\nimport { getFeishuQuestionDecision, isFeishuMessageAddressedToBot } from \"./question.js\";\nimport type { FeishuQuestionHandler } from \"./question.js\";\nimport { FeishuResourceDownloader } from \"./resource-downloader.js\";\nimport { mapDomain } from \"./sender.js\";\n\nexport interface FeishuGatewayRuntime {\n start(): Promise<void>;\n stop(): void;\n}\n\ninterface WsClientLike {\n start(params: { eventDispatcher: lark.EventDispatcher }): Promise<void>;\n close(params?: { force?: boolean }): void;\n}\n\nexport interface FeishuGatewayOptions {\n config: AppConfig;\n secrets: AppSecrets;\n ingestor: GatewayIngestor;\n questionHandler?: FeishuQuestionHandler;\n resourceDownloader?: FeishuResourceDownloader;\n attachmentVectorIndexer?: (messageId: string) => Promise<{ chunks: number; vectors: number }>;\n wsClientFactory?: (params: {\n appId: string;\n appSecret: string;\n domain: lark.Domain;\n onReady: () => void;\n onError: (error: Error) => void;\n onReconnecting: () => void;\n onReconnected: () => void;\n }) => WsClientLike;\n}\n\nfunction assertFeishuConfig(config: AppConfig, secrets: AppSecrets): void {\n if (!config.feishu.appId || !secrets.feishu.appSecret) {\n throw new Error(\"飞书配置不完整。请先运行 chattercatcher setup 或 chattercatcher settings。\");\n }\n}\n\nexport function createFeishuEventDispatcher(options: {\n config: AppConfig;\n ingestor: GatewayIngestor;\n questionHandler?: FeishuQuestionHandler;\n resourceDownloader?: FeishuResourceDownloader;\n attachmentVectorIndexer?: (messageId: string) => Promise<{ chunks: number; vectors: number }>;\n}): lark.EventDispatcher {\n const answeredMessageIds = new Set<string>();\n\n return new lark.EventDispatcher({}).register({\n \"im.message.receive_v1\": async (data: FeishuReceiveMessageEvent[\"event\"]) => {\n const payload = { event: data };\n\n if (options.questionHandler && isFeishuMessageAddressedToBot(payload)) {\n const platformMessageId = data?.message?.message_id;\n if (platformMessageId && answeredMessageIds.has(platformMessageId)) {\n console.log(\"飞书提问重复投递:已跳过回答。\");\n return;\n }\n\n const decision = getFeishuQuestionDecision(payload, options.config);\n if (decision.shouldAnswer) {\n if (platformMessageId) {\n answeredMessageIds.add(platformMessageId);\n }\n await options.questionHandler.handle(payload);\n console.log(\"飞书提问已回答:跳过知识库入库。\");\n return;\n }\n }\n\n const result: GatewayIngestAndDownloadResult = options.resourceDownloader\n ? await options.ingestor.ingestFeishuEventAndDownloadAttachments({\n payload,\n downloader: options.resourceDownloader,\n config: options.config,\n vectorIndexMessage: options.attachmentVectorIndexer,\n })\n : options.ingestor.ingestFeishuEvent(payload);\n\n if (!result.accepted) {\n console.log(`飞书消息未入库:${result.reason}`);\n return;\n }\n\n console.log(`飞书消息已入库:${result.messageId}`);\n if (result.duplicate) {\n console.log(\"飞书消息重复投递:已跳过附件处理和回答。\");\n return;\n }\n\n if (result.attachment?.downloaded) {\n console.log(`飞书附件已下载:${result.attachment.downloaded.storedPath}`);\n if (result.attachment.indexedMessageId) {\n console.log(`飞书附件已进入 RAG:${result.attachment.indexedMessageId}`);\n if (result.attachment.vectorIndexed) {\n console.log(\n `飞书附件向量索引完成:chunks=${result.attachment.vectorIndexed.chunks}, vectors=${result.attachment.vectorIndexed.vectors}`,\n );\n }\n } else if (result.attachment.skippedReason) {\n console.log(`飞书附件暂未进入 RAG:${result.attachment.skippedReason}`);\n }\n }\n\n if (options.questionHandler) {\n const decision = await options.questionHandler.handle(payload, {\n excludeMessageIds: result.messageId ? [result.messageId] : [],\n });\n if (!decision.shouldAnswer) {\n console.log(`飞书消息不触发回答:${decision.reason}`);\n }\n }\n },\n });\n}\n\nexport function createFeishuGateway(options: FeishuGatewayOptions): FeishuGatewayRuntime {\n assertFeishuConfig(options.config, options.secrets);\n\n const wsClient =\n options.wsClientFactory?.({\n appId: options.config.feishu.appId,\n appSecret: options.secrets.feishu.appSecret,\n domain: mapDomain(options.config.feishu.domain),\n onReady: () => console.log(\"飞书长连接已建立。\"),\n onError: (error) => console.error(`飞书长连接错误:${error.message}`),\n onReconnecting: () => console.log(\"飞书长连接正在重连。\"),\n onReconnected: () => console.log(\"飞书长连接已重连。\"),\n }) ??\n new lark.WSClient({\n appId: options.config.feishu.appId,\n appSecret: options.secrets.feishu.appSecret,\n domain: mapDomain(options.config.feishu.domain),\n loggerLevel: lark.LoggerLevel.info,\n source: \"chattercatcher\",\n onReady: () => console.log(\"飞书长连接已建立。\"),\n onError: (error) => console.error(`飞书长连接错误:${error.message}`),\n onReconnecting: () => console.log(\"飞书长连接正在重连。\"),\n onReconnected: () => console.log(\"飞书长连接已重连。\"),\n });\n\n const eventDispatcher = createFeishuEventDispatcher({\n config: options.config,\n ingestor: options.ingestor,\n questionHandler: options.questionHandler,\n resourceDownloader: options.resourceDownloader,\n attachmentVectorIndexer: options.attachmentVectorIndexer,\n });\n\n return {\n async start() {\n await wsClient.start({ eventDispatcher });\n },\n stop() {\n wsClient.close({ force: true });\n },\n };\n}\n","import type { Citation, EvidenceSource } from \"./types.js\";\n\nfunction isOpaqueId(value: string | undefined): boolean {\n return Boolean(value && /^(ou|oc|om|cli|on|un|uid)_?[a-z0-9]+/i.test(value));\n}\n\nfunction formatTime(value: string | undefined): string {\n if (!value) {\n return \"未知时间\";\n }\n\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return value;\n }\n\n const pad = (input: number) => String(input).padStart(2, \"0\");\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;\n}\n\nfunction formatSpeaker(source: EvidenceSource): string {\n if (source.type === \"file\") {\n return isOpaqueId(source.label) ? \"文件\" : `文件 ${source.label}`;\n }\n\n if (source.sender && !isOpaqueId(source.sender)) {\n return source.sender;\n }\n\n return \"群成员\";\n}\n\nfunction clipText(value: string, maxLength: number): string {\n const normalized = value.replace(/\\s+/g, \" \").trim();\n return normalized.length > maxLength ? `${normalized.slice(0, maxLength)}...` : normalized;\n}\n\nexport function formatCitation(citation: Citation, options: { maxTextLength?: number } = {}): string {\n const maxTextLength = options.maxTextLength ?? 120;\n const speaker = formatSpeaker(citation.source);\n const time = formatTime(citation.source.timestamp);\n const verb = citation.source.type === \"file\" ? \"记录\" : \"说\";\n return `[${citation.marker}] ${speaker}在 ${time} ${verb}:“${clipText(citation.text, maxTextLength)}”`;\n}\n\nexport function formatCitations(citations: Citation[], options: { maxTextLength?: number } = {}): string {\n return citations.map((citation) => formatCitation(citation, options)).join(\"\\n\");\n}\n","import type { ChatMessage, ChatModel, Citation, EvidenceBlock, GroundedAnswer } from \"./types.js\";\n\nexport interface BuildEvidencePromptOptions {\n maxEvidenceBlocks?: number;\n maxCharsPerBlock?: number;\n}\n\nexport interface EvidencePrompt {\n messages: ChatMessage[];\n citations: Citation[];\n}\n\nconst DEFAULT_MAX_EVIDENCE_BLOCKS = 8;\nconst DEFAULT_MAX_CHARS_PER_BLOCK = 1200;\nconst SCORE_TIE_THRESHOLD = 0.15;\n\nfunction parseTimestamp(value: string | undefined): number {\n if (!value) {\n return 0;\n }\n\n const time = Date.parse(value);\n return Number.isFinite(time) ? time : 0;\n}\n\nexport function rankEvidenceForPrompt(evidence: EvidenceBlock[]): EvidenceBlock[] {\n return [...evidence].sort((left, right) => {\n const scoreDiff = right.score - left.score;\n if (Math.abs(scoreDiff) > SCORE_TIE_THRESHOLD) {\n return scoreDiff;\n }\n\n const timeDiff = parseTimestamp(right.source.timestamp) - parseTimestamp(left.source.timestamp);\n if (timeDiff !== 0) {\n return timeDiff;\n }\n\n return scoreDiff;\n });\n}\n\nexport function buildEvidencePrompt(\n question: string,\n evidence: EvidenceBlock[],\n options: BuildEvidencePromptOptions = {},\n): EvidencePrompt {\n if (evidence.length === 0) {\n throw new Error(\"RAG evidence is required before answer generation.\");\n }\n\n const maxEvidenceBlocks = options.maxEvidenceBlocks ?? DEFAULT_MAX_EVIDENCE_BLOCKS;\n const maxCharsPerBlock = options.maxCharsPerBlock ?? DEFAULT_MAX_CHARS_PER_BLOCK;\n const selected = rankEvidenceForPrompt(evidence).slice(0, maxEvidenceBlocks);\n\n const citations = selected.map<Citation>((item, index) => ({\n marker: `S${index + 1}`,\n evidenceId: item.id,\n source: item.source,\n text: item.text,\n }));\n\n const evidenceText = selected\n .map((item, index) => {\n const marker = citations[index]?.marker;\n const clippedText =\n item.text.length > maxCharsPerBlock ? `${item.text.slice(0, maxCharsPerBlock)}...` : item.text;\n const sourceParts = [\n item.source.label,\n item.source.sender ? `发送人:${item.source.sender}` : undefined,\n item.source.timestamp ? `时间:${item.source.timestamp}` : undefined,\n item.source.location ? `位置:${item.source.location}` : undefined,\n ].filter(Boolean);\n\n return `[${marker}]\\n来源:${sourceParts.join(\";\")}\\n内容:${clippedText}`;\n })\n .join(\"\\n\\n\");\n\n return {\n citations,\n messages: [\n {\n role: \"system\",\n content:\n \"你是 ChatterCatcher 的问答模块。只能根据提供的检索证据回答,必须简短直接。事实性结论必须引用 [S1] 这样的来源标记。证据不足时说不知道,不要猜。若证据互相矛盾,优先采用时间更新且表述明确的证据;如果较新的证据只是讨论、猜测或不确定表达,不要把它当作确定更新。\",\n },\n {\n role: \"user\",\n content: `问题:${question}\\n\\n证据处理规则:\\n1. 先判断证据是否足以回答问题。\\n2. 同一事项出现多个版本时,默认较新的明确消息优先。\\n3. 回答只引用实际支撑结论的证据。\\n\\n检索证据:\\n${evidenceText}`,\n },\n ],\n };\n}\n\nexport async function generateGroundedAnswer(input: {\n question: string;\n evidence: EvidenceBlock[];\n model: ChatModel;\n}): Promise<GroundedAnswer> {\n const prompt = buildEvidencePrompt(input.question, input.evidence);\n const answer = await input.model.complete(prompt.messages);\n\n return {\n answer,\n citations: prompt.citations,\n };\n}\n","import { generateGroundedAnswer } from \"./answer.js\";\nimport type { Retriever } from \"./retriever.js\";\nimport type { ChatModel, GroundedAnswer } from \"./types.js\";\n\nexport interface AskWithRagInput {\n question: string;\n retriever: Retriever;\n model: ChatModel;\n}\n\nexport async function askWithRag(input: AskWithRagInput): Promise<GroundedAnswer> {\n const evidence = await input.retriever.retrieve(input.question);\n\n if (evidence.length === 0) {\n return {\n answer: \"不知道。当前本地知识库没有检索到足够证据。\",\n citations: [],\n };\n }\n\n return generateGroundedAnswer({\n question: input.question,\n evidence,\n model: input.model,\n });\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { formatCitations } from \"../rag/citations.js\";\nimport { createHybridRetriever } from \"../rag/factory.js\";\nimport { askWithRag } from \"../rag/qa-service.js\";\nimport type { ChatModel } from \"../rag/types.js\";\nimport type { MessageSender } from \"./sender.js\";\nimport type { FeishuReceiveMessageEvent } from \"./normalize.js\";\n\nexport interface FeishuQuestionHandlerOptions {\n config: AppConfig;\n secrets: AppSecrets;\n database: SqliteDatabase;\n model: ChatModel;\n sender: MessageSender;\n thinkingEmojiType?: string;\n}\n\nexport interface FeishuQuestionDecision {\n shouldAnswer: boolean;\n question?: string;\n chatId?: string;\n reason?: string;\n}\n\nfunction parseTextContent(content: string | undefined): string {\n if (!content) {\n return \"\";\n }\n\n try {\n const parsed = JSON.parse(content) as { text?: unknown };\n return typeof parsed.text === \"string\" ? parsed.text : \"\";\n } catch {\n return content;\n }\n}\n\nfunction stripMentions(text: string, mentions: NonNullable<NonNullable<FeishuReceiveMessageEvent[\"event\"]>[\"message\"]>[\"mentions\"]): string {\n let result = text;\n\n for (const mention of mentions ?? []) {\n for (const token of [mention.key, mention.name, mention.name ? `@${mention.name}` : undefined]) {\n if (token) {\n result = result.replaceAll(token, \" \");\n }\n }\n }\n\n return result.replace(/@\\s*ChatterCatcher/gi, \" \").replace(/@/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\nexport function isFeishuMessageAddressedToBot(payload: FeishuReceiveMessageEvent): boolean {\n const message = payload.event?.message;\n if (!message || message.message_type !== \"text\") {\n return false;\n }\n\n const mentions = message.mentions ?? [];\n const text = parseTextContent(message.content);\n return mentions.length > 0 || /@?ChatterCatcher/i.test(text);\n}\n\nexport function getFeishuQuestionDecision(\n payload: FeishuReceiveMessageEvent,\n config: AppConfig,\n): FeishuQuestionDecision {\n const message = payload.event?.message;\n if (!message?.chat_id || message.message_type !== \"text\") {\n return {\n shouldAnswer: false,\n reason: \"不是可回答的文本消息。\",\n };\n }\n\n const mentions = message.mentions ?? [];\n const text = parseTextContent(message.content);\n const hasMention = isFeishuMessageAddressedToBot(payload);\n\n if (config.feishu.requireMention && !hasMention) {\n return {\n shouldAnswer: false,\n reason: \"群聊配置为必须 @ 后回答。\",\n };\n }\n\n const question = stripMentions(text, mentions);\n if (!question) {\n return {\n shouldAnswer: false,\n reason: \"没有可回答的问题文本。\",\n };\n }\n\n return {\n shouldAnswer: true,\n question,\n chatId: message.chat_id,\n };\n}\n\nexport class FeishuQuestionHandler {\n constructor(private readonly options: FeishuQuestionHandlerOptions) {}\n\n private async sendResponse(chatId: string, messageId: string | undefined, text: string): Promise<void> {\n if (messageId && this.options.sender.replyTextToMessage) {\n try {\n await this.options.sender.replyTextToMessage(messageId, text);\n return;\n } catch (error) {\n console.log(`飞书回复原消息失败,退回群消息:${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n await this.options.sender.sendTextToChat(chatId, text);\n }\n\n private async acknowledgeQuestion(chatId: string, messageId: string | undefined): Promise<void> {\n if (!messageId) {\n return;\n }\n\n if (this.options.sender.addReactionToMessage) {\n try {\n await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? \"keyboard\");\n return;\n } catch (error) {\n console.log(`飞书提问表情反馈失败,改用文字反馈:${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n await this.sendResponse(chatId, messageId, \"收到,正在查。\");\n }\n\n async handle(\n payload: FeishuReceiveMessageEvent,\n options: { excludeMessageIds?: string[] } = {},\n ): Promise<FeishuQuestionDecision> {\n const decision = getFeishuQuestionDecision(payload, this.options.config);\n if (!decision.shouldAnswer || !decision.question || !decision.chatId) {\n return decision;\n }\n\n const questionMessageId = payload.event?.message?.message_id;\n await this.acknowledgeQuestion(decision.chatId, questionMessageId);\n\n const { retriever, close } = await createHybridRetriever({\n config: this.options.config,\n secrets: this.options.secrets,\n messages: new MessageRepository(this.options.database),\n excludeMessageIds: options.excludeMessageIds,\n });\n\n try {\n try {\n const result = await askWithRag({\n question: decision.question,\n retriever,\n model: this.options.model,\n });\n const citations = formatCitations(result.citations);\n const text = citations ? `${result.answer}\\n\\n引用:\\n${citations}` : result.answer;\n await this.sendResponse(decision.chatId, questionMessageId, text);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n await this.sendResponse(decision.chatId, questionMessageId, `暂时无法回答:${message}`);\n }\n return decision;\n } finally {\n close();\n }\n }\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\n\nexport interface MessageSender {\n sendTextToChat(chatId: string, text: string): Promise<void>;\n replyTextToMessage?(messageId: string, text: string): Promise<void>;\n addReactionToMessage?(messageId: string, emojiType: string): Promise<void>;\n}\n\ninterface FeishuClientLike {\n im: {\n v1?: {\n message: {\n create(payload: {\n data: {\n receive_id: string;\n msg_type: string;\n content: string;\n };\n params: {\n receive_id_type: \"chat_id\";\n };\n }): Promise<unknown>;\n reply?: (payload: {\n path: {\n message_id: string;\n };\n data: {\n msg_type: string;\n content: string;\n };\n }) => Promise<unknown>;\n };\n messageReaction?: {\n create(payload: {\n path: {\n message_id: string;\n };\n data: {\n reaction_type: {\n emoji_type: string;\n };\n };\n }): Promise<unknown>;\n };\n };\n message?: {\n create(payload: {\n data: {\n receive_id: string;\n msg_type: string;\n content: string;\n };\n params: {\n receive_id_type: \"chat_id\";\n };\n }): Promise<unknown>;\n reply?: (payload: {\n path: {\n message_id: string;\n };\n data: {\n msg_type: string;\n content: string;\n };\n }) => Promise<unknown>;\n };\n };\n}\n\nexport function mapDomain(domain: AppConfig[\"feishu\"][\"domain\"]): lark.Domain {\n return domain === \"lark\" ? lark.Domain.Lark : lark.Domain.Feishu;\n}\n\nexport class FeishuMessageSender implements MessageSender {\n constructor(private readonly client: FeishuClientLike) {}\n\n static fromConfig(config: AppConfig, secrets: AppSecrets): FeishuMessageSender {\n const client = new lark.Client({\n appId: config.feishu.appId,\n appSecret: secrets.feishu.appSecret,\n domain: mapDomain(config.feishu.domain),\n }) as FeishuClientLike;\n\n return new FeishuMessageSender(client);\n }\n\n async sendTextToChat(chatId: string, text: string): Promise<void> {\n const payload = {\n data: {\n receive_id: chatId,\n msg_type: \"text\",\n content: JSON.stringify({ text }),\n },\n params: {\n receive_id_type: \"chat_id\" as const,\n },\n };\n\n if (this.client.im.v1?.message.create) {\n await this.client.im.v1.message.create(payload);\n return;\n }\n\n if (this.client.im.message?.create) {\n await this.client.im.message.create(payload);\n return;\n }\n\n {\n throw new Error(\"当前飞书 SDK 不支持消息发送接口。\");\n }\n }\n\n async replyTextToMessage(messageId: string, text: string): Promise<void> {\n const payload = {\n path: {\n message_id: messageId,\n },\n data: {\n msg_type: \"text\",\n content: JSON.stringify({ text }),\n },\n };\n\n if (this.client.im.v1?.message.reply) {\n await this.client.im.v1.message.reply(payload);\n return;\n }\n\n if (this.client.im.message?.reply) {\n await this.client.im.message.reply(payload);\n return;\n }\n\n throw new Error(\"当前飞书 SDK 不支持消息回复接口。\");\n }\n\n async addReactionToMessage(messageId: string, emojiType: string): Promise<void> {\n if (!this.client.im.v1?.messageReaction?.create) {\n throw new Error(\"当前飞书 SDK 不支持消息表情回复接口。\");\n }\n\n await this.client.im.v1.messageReaction.create({\n path: {\n message_id: messageId,\n },\n data: {\n reaction_type: {\n emoji_type: emojiType,\n },\n },\n });\n }\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport { mapDomain } from \"./sender.js\";\nimport type { FeishuAttachmentMetadata } from \"./normalize.js\";\n\ninterface DownloadResponseLike {\n writeFile(filePath: string): Promise<unknown>;\n}\n\ninterface FeishuResourceClientLike {\n im: {\n v1?: {\n messageResource?: {\n get(payload: {\n params: { type: string };\n path: { message_id: string; file_key: string };\n }): Promise<DownloadResponseLike>;\n };\n };\n messageResource?: {\n get(payload: {\n params: { type: string };\n path: { message_id: string; file_key: string };\n }): Promise<DownloadResponseLike>;\n };\n };\n}\n\nexport interface FeishuDownloadResourceInput {\n messageId: string;\n attachment: FeishuAttachmentMetadata;\n}\n\nexport interface FeishuDownloadedResource {\n messageId: string;\n fileKey: string;\n fileName: string;\n resourceType: string;\n storedPath: string;\n}\n\nconst RESOURCE_TYPE_BY_KIND: Record<FeishuAttachmentMetadata[\"kind\"], string> = {\n file: \"file\",\n image: \"image\",\n audio: \"audio\",\n media: \"media\",\n};\n\nconst DEFAULT_EXTENSION_BY_KIND: Record<FeishuAttachmentMetadata[\"kind\"], string> = {\n file: \".bin\",\n image: \".jpg\",\n audio: \".mp3\",\n media: \".mp4\",\n};\n\nfunction sanitizeFileName(value: string): string {\n const sanitized = value.replace(/[<>:\"/\\\\|?*\\u0000-\\u001f]/g, \"_\").trim();\n return sanitized || \"feishu-resource\";\n}\n\nfunction buildStoredFileName(input: FeishuDownloadResourceInput): string {\n const rawName = input.attachment.fileName || `${input.attachment.fileKey}${DEFAULT_EXTENSION_BY_KIND[input.attachment.kind]}`;\n return `${input.messageId}-${sanitizeFileName(rawName)}`;\n}\n\nexport class FeishuResourceDownloader {\n constructor(\n private readonly client: FeishuResourceClientLike,\n private readonly dataDir: string,\n ) {}\n\n static fromConfig(config: AppConfig, secrets: AppSecrets): FeishuResourceDownloader {\n const client = new lark.Client({\n appId: config.feishu.appId,\n appSecret: secrets.feishu.appSecret,\n domain: mapDomain(config.feishu.domain),\n }) as FeishuResourceClientLike;\n\n return new FeishuResourceDownloader(client, resolveHomePath(config.storage.dataDir));\n }\n\n async download(input: FeishuDownloadResourceInput): Promise<FeishuDownloadedResource> {\n const resourceType = RESOURCE_TYPE_BY_KIND[input.attachment.kind];\n const targetDir = path.join(this.dataDir, \"files\", \"feishu\");\n await fs.mkdir(targetDir, { recursive: true });\n\n const fileName = buildStoredFileName(input);\n const storedPath = path.join(targetDir, fileName);\n const payload = {\n params: { type: resourceType },\n path: { message_id: input.messageId, file_key: input.attachment.fileKey },\n };\n\n const api = this.client.im.v1?.messageResource?.get ?? this.client.im.messageResource?.get;\n if (!api) {\n throw new Error(\"当前飞书 SDK 不支持 messageResource.get,无法下载消息资源。\");\n }\n\n const response = await api(payload);\n await response.writeFile(storedPath);\n\n return {\n messageId: input.messageId,\n fileKey: input.attachment.fileKey,\n fileName,\n resourceType,\n storedPath,\n };\n }\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport { describeSupportedParseTypes, isSupportedParseFile, parseFileToText } from \"./parser.js\";\nimport type { FileJobRepository } from \"./jobs.js\";\n\nexport interface IngestLocalFileResult {\n messageId: string;\n sourcePath: string;\n storedPath: string;\n fileName: string;\n bytes: number;\n characters: number;\n parser: string;\n warnings: string[];\n jobId?: string;\n}\n\nexport function isSupportedTextFile(filePath: string): boolean {\n return isSupportedParseFile(filePath);\n}\n\nfunction ensureSupportedTextFile(filePath: string): void {\n if (!isSupportedTextFile(filePath)) {\n const extension = path.extname(filePath).toLowerCase();\n throw new Error(`暂不支持该文件类型:${extension || \"无扩展名\"}。当前支持 ${describeSupportedParseTypes()}。`);\n }\n}\n\nfunction stableStoredName(sourcePath: string, fileName: string): string {\n const digest = crypto.createHash(\"sha256\").update(sourcePath).digest(\"hex\").slice(0, 16);\n return `${digest}-${fileName}`;\n}\n\nexport async function ingestLocalFile(input: {\n config: AppConfig;\n messages: MessageRepository;\n filePath: string;\n jobs?: FileJobRepository;\n}): Promise<IngestLocalFileResult> {\n const sourcePath = path.resolve(input.filePath);\n const fileName = path.basename(sourcePath);\n const jobId = input.jobs?.start({ sourcePath, fileName });\n\n try {\n ensureSupportedTextFile(sourcePath);\n\n const stat = await fs.stat(sourcePath);\n if (!stat.isFile()) {\n throw new Error(`不是文件:${sourcePath}`);\n }\n\n const parsed = await parseFileToText(sourcePath);\n const text = parsed.text.trim();\n if (!text) {\n throw new Error(`文件没有可索引文本:${sourcePath}`);\n }\n\n const fileDir = path.join(resolveHomePath(input.config.storage.dataDir), \"files\");\n await fs.mkdir(fileDir, { recursive: true });\n\n const storedPath = path.join(fileDir, stableStoredName(sourcePath, fileName));\n await fs.copyFile(sourcePath, storedPath);\n\n const messageId = input.messages.ingest({\n platform: \"local-file\",\n platformChatId: \"local-files\",\n chatName: \"文件库\",\n platformMessageId: sourcePath,\n senderId: \"local-file\",\n senderName: fileName,\n messageType: \"file\",\n text,\n sentAt: stat.mtime.toISOString(),\n rawPayload: {\n sourcePath,\n storedPath,\n bytes: stat.size,\n fileName,\n parser: parsed.parser,\n parserWarnings: parsed.warnings,\n },\n });\n\n input.jobs?.complete({\n id: jobId ?? \"\",\n storedPath,\n parser: parsed.parser,\n messageId,\n bytes: stat.size,\n characters: text.length,\n warnings: parsed.warnings,\n });\n\n return {\n messageId,\n sourcePath,\n storedPath,\n fileName,\n bytes: stat.size,\n characters: text.length,\n parser: parsed.parser,\n warnings: parsed.warnings,\n jobId,\n };\n } catch (error) {\n if (jobId) {\n input.jobs?.fail({\n id: jobId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n throw error;\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport mammoth from \"mammoth\";\nimport { PDFParse } from \"pdf-parse\";\n\nconst TEXT_EXTENSIONS = new Set([\".txt\", \".md\", \".markdown\", \".json\", \".csv\", \".tsv\", \".log\"]);\nconst DOCX_EXTENSIONS = new Set([\".docx\"]);\nconst PDF_EXTENSIONS = new Set([\".pdf\"]);\n\nexport interface ParsedFile {\n text: string;\n parser: \"text\" | \"docx\" | \"pdf\";\n warnings: string[];\n}\n\nexport function isSupportedParseFile(filePath: string): boolean {\n const extension = path.extname(filePath).toLowerCase();\n return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);\n}\n\nexport function describeSupportedParseTypes(): string {\n return \"txt、md、json、csv、tsv、log、docx、pdf\";\n}\n\nexport async function parseFileToText(filePath: string): Promise<ParsedFile> {\n const extension = path.extname(filePath).toLowerCase();\n\n if (TEXT_EXTENSIONS.has(extension)) {\n return {\n text: await fs.readFile(filePath, \"utf8\"),\n parser: \"text\",\n warnings: [],\n };\n }\n\n if (DOCX_EXTENSIONS.has(extension)) {\n const result = await mammoth.extractRawText({ path: filePath });\n return {\n text: result.value,\n parser: \"docx\",\n warnings: result.messages.map((message) => message.message),\n };\n }\n\n if (PDF_EXTENSIONS.has(extension)) {\n const buffer = await fs.readFile(filePath);\n const parser = new PDFParse({ data: buffer });\n try {\n const result = await parser.getText();\n return {\n text: result.text,\n parser: \"pdf\",\n warnings: [],\n };\n } finally {\n await parser.destroy();\n }\n }\n\n throw new Error(`暂不支持该文件类型:${extension || \"无扩展名\"}。当前支持 ${describeSupportedParseTypes()}。`);\n}\n","import type { IngestMessageInput } from \"../messages/types.js\";\n\ntype JsonObject = Record<string, unknown>;\n\nexport interface FeishuAttachmentMetadata {\n platform: \"feishu\";\n kind: \"file\" | \"image\" | \"audio\" | \"media\";\n fileKey: string;\n fileName?: string;\n mimeType?: string;\n size?: number;\n}\n\nexport interface FeishuReceiveMessageEvent {\n event?: {\n sender?: {\n sender_id?: {\n open_id?: string;\n user_id?: string;\n union_id?: string;\n };\n };\n message?: {\n message_id?: string;\n chat_id?: string;\n chat_type?: string;\n create_time?: string;\n message_type?: string;\n content?: string;\n mentions?: Array<{\n name?: string;\n key?: string;\n id?: {\n open_id?: string;\n user_id?: string;\n union_id?: string;\n };\n }>;\n };\n };\n}\n\nfunction asObject(value: unknown): JsonObject {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) ? (value as JsonObject) : {};\n}\n\nfunction parseContent(content: string | undefined): JsonObject {\n if (!content) {\n return {};\n }\n\n try {\n return asObject(JSON.parse(content));\n } catch {\n return { text: content };\n }\n}\n\nfunction stringifyUnknown(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n\n return \"\";\n}\n\nfunction numberUnknown(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value;\n }\n\n if (typeof value === \"string\") {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n\n return undefined;\n}\n\nfunction extractPostText(content: JsonObject): string {\n const post = asObject(content.post);\n const zhCn = asObject(post.zh_cn ?? post[\"zh-CN\"] ?? post.en_us ?? post[\"en-US\"]);\n const title = stringifyUnknown(zhCn.title);\n const blocks = Array.isArray(zhCn.content) ? zhCn.content : [];\n const parts: string[] = [];\n\n if (title) {\n parts.push(title);\n }\n\n for (const block of blocks) {\n if (!Array.isArray(block)) {\n continue;\n }\n\n for (const item of block) {\n const object = asObject(item);\n const text = stringifyUnknown(object.text);\n if (text) {\n parts.push(text);\n }\n }\n }\n\n return parts.join(\" \").trim();\n}\n\nexport function extractFeishuAttachment(\n messageType: string,\n content: JsonObject,\n): FeishuAttachmentMetadata | undefined {\n if (messageType === \"image\") {\n const fileKey = stringifyUnknown(content.image_key);\n return fileKey ? { platform: \"feishu\", kind: \"image\", fileKey } : undefined;\n }\n\n if (messageType === \"audio\") {\n const fileKey = stringifyUnknown(content.file_key);\n return fileKey ? { platform: \"feishu\", kind: \"audio\", fileKey } : undefined;\n }\n\n if (messageType !== \"file\" && messageType !== \"media\") {\n return undefined;\n }\n\n const fileKey = stringifyUnknown(content.file_key);\n if (!fileKey) {\n return undefined;\n }\n\n return {\n platform: \"feishu\",\n kind: messageType,\n fileKey,\n fileName: stringifyUnknown(content.file_name) || undefined,\n mimeType: stringifyUnknown(content.mime_type) || undefined,\n size: numberUnknown(content.file_size ?? content.size),\n };\n}\n\nfunction extractMessageText(messageType: string, content: JsonObject): string {\n if (messageType === \"text\") {\n return stringifyUnknown(content.text).trim();\n }\n\n if (messageType === \"post\") {\n return extractPostText(content);\n }\n\n if (messageType === \"image\") {\n return `[图片] ${stringifyUnknown(content.image_key)}`.trim();\n }\n\n if (messageType === \"file\") {\n return `[文件] ${stringifyUnknown(content.file_name) || stringifyUnknown(content.file_key)}`.trim();\n }\n\n if (messageType === \"audio\") {\n return `[语音] ${stringifyUnknown(content.file_key)}`.trim();\n }\n\n if (messageType === \"media\") {\n return `[媒体] ${stringifyUnknown(content.file_name) || stringifyUnknown(content.file_key)}`.trim();\n }\n\n const fallback = Object.entries(content)\n .map(([key, value]) => `${key}: ${stringifyUnknown(value)}`)\n .filter((line) => !line.endsWith(\": \"))\n .join(\" \");\n\n return fallback || `[${messageType}]`;\n}\n\nfunction normalizeTimestamp(createTime: string | undefined): string {\n if (!createTime) {\n return new Date().toISOString();\n }\n\n const numeric = Number(createTime);\n if (Number.isFinite(numeric)) {\n const milliseconds = createTime.length <= 10 ? numeric * 1000 : numeric;\n return new Date(milliseconds).toISOString();\n }\n\n const date = new Date(createTime);\n if (Number.isNaN(date.getTime())) {\n return new Date().toISOString();\n }\n\n return date.toISOString();\n}\n\nexport function normalizeFeishuReceiveMessageEvent(payload: FeishuReceiveMessageEvent): IngestMessageInput | null {\n const event = payload.event;\n const message = event?.message;\n if (!event || !message?.message_id || !message.chat_id) {\n return null;\n }\n\n const messageType = message.message_type || \"unknown\";\n const content = parseContent(message.content);\n const text = extractMessageText(messageType, content);\n if (!text) {\n return null;\n }\n\n const senderId =\n event.sender?.sender_id?.open_id ||\n event.sender?.sender_id?.user_id ||\n event.sender?.sender_id?.union_id ||\n \"unknown\";\n\n return {\n platform: \"feishu\",\n platformChatId: message.chat_id,\n chatName: message.chat_id,\n platformMessageId: message.message_id,\n senderId,\n senderName: senderId,\n messageType,\n text,\n sentAt: normalizeTimestamp(message.create_time),\n rawPayload: {\n platform: \"feishu\",\n raw: payload,\n content,\n attachment: extractFeishuAttachment(messageType, content),\n },\n };\n}\n","import type { SqliteDatabase } from \"../db/database.js\";\nimport {\n normalizeFeishuReceiveMessageEvent,\n type FeishuAttachmentMetadata,\n type FeishuReceiveMessageEvent,\n} from \"../feishu/normalize.js\";\nimport type { FeishuDownloadedResource, FeishuResourceDownloader } from \"../feishu/resource-downloader.js\";\nimport { isSupportedTextFile, ingestLocalFile } from \"../files/ingest.js\";\nimport { FileJobRepository } from \"../files/jobs.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport type { IngestMessageInput } from \"../messages/types.js\";\n\nexport interface GatewayIngestResult {\n accepted: boolean;\n messageId?: string;\n message?: IngestMessageInput;\n duplicate?: boolean;\n reason?: string;\n}\n\nexport interface GatewayAttachmentIngestResult {\n downloaded?: FeishuDownloadedResource;\n indexedMessageId?: string;\n vectorIndexed?: {\n chunks: number;\n vectors: number;\n };\n skippedReason?: string;\n}\n\nexport interface GatewayIngestAndDownloadResult extends GatewayIngestResult {\n attachment?: GatewayAttachmentIngestResult;\n}\n\nfunction extractAttachment(message: IngestMessageInput): FeishuAttachmentMetadata | undefined {\n const raw = message.rawPayload;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return undefined;\n }\n\n const attachment = (raw as { attachment?: unknown }).attachment;\n if (!attachment || typeof attachment !== \"object\" || Array.isArray(attachment)) {\n return undefined;\n }\n\n const candidate = attachment as Partial<FeishuAttachmentMetadata>;\n if (candidate.platform !== \"feishu\" || !candidate.kind || !candidate.fileKey) {\n return undefined;\n }\n\n return candidate as FeishuAttachmentMetadata;\n}\n\nexport class GatewayIngestor {\n private readonly messages: MessageRepository;\n private readonly jobs: FileJobRepository;\n\n constructor(database: SqliteDatabase) {\n this.messages = new MessageRepository(database);\n this.jobs = new FileJobRepository(database);\n }\n\n ingestFeishuEvent(payload: FeishuReceiveMessageEvent): GatewayIngestResult {\n const normalized = normalizeFeishuReceiveMessageEvent(payload);\n if (!normalized) {\n return {\n accepted: false,\n reason: \"事件不是可入库的飞书消息。\",\n };\n }\n\n const duplicate = this.messages.hasPlatformMessage(normalized.platform, normalized.platformMessageId);\n const messageId = this.messages.ingest(normalized);\n return {\n accepted: true,\n messageId,\n message: normalized,\n duplicate,\n };\n }\n\n async ingestFeishuEventAndDownloadAttachments(input: {\n payload: FeishuReceiveMessageEvent;\n downloader: FeishuResourceDownloader;\n config: Parameters<typeof ingestLocalFile>[0][\"config\"];\n vectorIndexMessage?: (messageId: string) => Promise<{ chunks: number; vectors: number }>;\n }): Promise<GatewayIngestAndDownloadResult> {\n const result = this.ingestFeishuEvent(input.payload);\n if (!result.accepted || !result.messageId || !result.message || result.duplicate) {\n return result;\n }\n\n const attachment = extractAttachment(result.message);\n if (!attachment) {\n return result;\n }\n\n const downloaded = await input.downloader.download({\n messageId: result.message.platformMessageId,\n attachment,\n });\n\n if (!isSupportedTextFile(downloaded.storedPath)) {\n return {\n ...result,\n attachment: {\n downloaded,\n skippedReason: \"附件已下载,但当前文件类型暂不支持解析。\",\n },\n };\n }\n\n const indexedMessageId = await ingestLocalFile({\n config: input.config,\n messages: this.messages,\n jobs: this.jobs,\n filePath: downloaded.storedPath,\n }).then((file) => file.messageId);\n const vectorIndexed = input.vectorIndexMessage ? await input.vectorIndexMessage(indexedMessageId) : undefined;\n\n return {\n ...result,\n attachment: {\n downloaded,\n indexedMessageId,\n vectorIndexed,\n },\n };\n }\n}\n","import fs from \"node:fs/promises\";\nimport { watch } from \"node:fs\";\nimport path from \"node:path\";\nimport { getChatterCatcherHome } from \"../config/paths.js\";\n\nexport interface LogFileInfo {\n name: string;\n path: string;\n updatedAt: Date;\n bytes: number;\n}\n\nexport interface LogTailResult {\n file: LogFileInfo;\n content: string;\n}\n\nexport function getLogsDirectory(): string {\n return path.join(getChatterCatcherHome(), \"logs\");\n}\n\nexport function resolveLogPath(fileName: string, logsDir = getLogsDirectory()): string {\n return path.isAbsolute(fileName) ? fileName : path.join(logsDir, fileName);\n}\n\nexport function normalizeLineCount(value: number | string | undefined, fallback = 200): number {\n const parsed = Number(value ?? fallback);\n return Number.isFinite(parsed) ? Math.min(Math.max(Math.trunc(parsed), 1), 10_000) : fallback;\n}\n\nexport async function listLogFiles(logsDir = getLogsDirectory()): Promise<LogFileInfo[]> {\n let entries: Array<{ isFile: () => boolean; name: string }>;\n try {\n entries = await fs.readdir(logsDir, { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return [];\n }\n\n throw error;\n }\n\n const files = await Promise.all(\n entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".log\"))\n .map(async (entry) => {\n const filePath = path.join(logsDir, entry.name);\n const stats = await fs.stat(filePath);\n return {\n name: entry.name,\n path: filePath,\n updatedAt: stats.mtime,\n bytes: stats.size,\n };\n }),\n );\n\n return files.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());\n}\n\nfunction tailLines(content: string, lines: number): string {\n const normalized = content.replace(/\\r\\n/g, \"\\n\");\n const parts = normalized.endsWith(\"\\n\") ? normalized.slice(0, -1).split(\"\\n\") : normalized.split(\"\\n\");\n return parts.slice(-lines).join(\"\\n\");\n}\n\nexport async function readLogTail(input: { filePath: string; lines?: number }): Promise<LogTailResult> {\n const stats = await fs.stat(input.filePath);\n const content = await fs.readFile(input.filePath, \"utf8\");\n return {\n file: {\n name: path.basename(input.filePath),\n path: input.filePath,\n updatedAt: stats.mtime,\n bytes: stats.size,\n },\n content: tailLines(content, normalizeLineCount(input.lines)),\n };\n}\n\nexport async function readLatestLogTail(input: {\n fileName?: string;\n lines?: number;\n logsDir?: string;\n} = {}): Promise<LogTailResult | null> {\n if (input.fileName) {\n return readLogTail({\n filePath: resolveLogPath(input.fileName, input.logsDir),\n lines: input.lines,\n });\n }\n\n const [latest] = await listLogFiles(input.logsDir);\n if (!latest) {\n return null;\n }\n\n return readLogTail({ filePath: latest.path, lines: input.lines });\n}\n\nexport async function followLogFile(input: {\n filePath: string;\n onChunk: (chunk: string) => void;\n onError?: (error: Error) => void;\n}): Promise<() => void> {\n let offset = (await fs.stat(input.filePath)).size;\n const directory = path.dirname(input.filePath);\n const fileName = path.basename(input.filePath);\n\n async function readAppended(): Promise<void> {\n const stats = await fs.stat(input.filePath);\n if (stats.size < offset) {\n offset = 0;\n }\n\n if (stats.size === offset) {\n return;\n }\n\n const handle = await fs.open(input.filePath, \"r\");\n try {\n const length = stats.size - offset;\n const buffer = Buffer.alloc(length);\n await handle.read(buffer, 0, length, offset);\n offset = stats.size;\n input.onChunk(buffer.toString(\"utf8\"));\n } finally {\n await handle.close();\n }\n }\n\n const watcher = watch(directory, (eventType, changedFileName) => {\n if (eventType !== \"change\" || changedFileName?.toString() !== fileName) {\n return;\n }\n\n void readAppended().catch((error: unknown) => {\n input.onError?.(error instanceof Error ? error : new Error(String(error)));\n });\n });\n\n return () => watcher.close();\n}\n","import type { EmbeddingModel } from \"./embedding.js\";\nimport type { VectorRecord, VectorStore } from \"./vector-store.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport type { MessageSearchResult } from \"../messages/types.js\";\n\nexport interface VectorIndexStats {\n chunks: number;\n vectors: number;\n}\n\nexport async function indexMessageChunks(input: {\n messages: MessageRepository;\n embedding: EmbeddingModel;\n store: VectorStore;\n limit?: number;\n messageIds?: string[];\n}): Promise<VectorIndexStats> {\n const chunks = input.messageIds\n ? input.messages.listMessageChunksByMessageIds(input.messageIds, input.limit ?? 10000)\n : input.messages.listAllMessageChunks(input.limit ?? 10000);\n if (chunks.length === 0) {\n return { chunks: 0, vectors: 0 };\n }\n\n const vectors = await input.embedding.embedBatch(chunks.map((chunk) => chunk.text));\n const records: VectorRecord[] = [];\n\n for (const [index, chunk] of chunks.entries()) {\n const vector = vectors[index];\n if (!vector || vector.length === 0) {\n continue;\n }\n\n records.push({\n id: chunk.chunkId,\n vector,\n evidence: {\n id: chunk.chunkId,\n text: chunk.text,\n score: 1,\n source: toEvidenceSource(chunk),\n },\n });\n }\n\n await input.store.upsert(records);\n\n return {\n chunks: chunks.length,\n vectors: records.length,\n };\n}\n\nfunction toEvidenceSource(chunk: MessageSearchResult): VectorRecord[\"evidence\"][\"source\"] {\n if (chunk.messageType === \"file\") {\n return {\n type: \"file\",\n label: chunk.senderName,\n timestamp: chunk.sentAt,\n };\n }\n\n return {\n type: \"message\",\n label: chunk.chatName,\n sender: chunk.senderName,\n timestamp: chunk.sentAt,\n };\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { createEmbeddingModel } from \"../llm/openai-compatible.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { hasEmbeddingConfig } from \"./factory.js\";\nimport { indexMessageChunks } from \"./indexer.js\";\nimport { SQLiteVectorStore } from \"./sqlite-vector-store.js\";\n\nexport interface ManualMessageIndexResult {\n status: \"completed\" | \"skipped\";\n reason?: string;\n chunks: number;\n vectors: number;\n startedAt: string;\n finishedAt: string;\n}\n\nexport async function processMessagesNow(input: {\n config: AppConfig;\n secrets: AppSecrets;\n database: SqliteDatabase;\n limit?: number;\n}): Promise<ManualMessageIndexResult> {\n const startedAt = new Date().toISOString();\n\n if (!hasEmbeddingConfig(input.config, input.secrets)) {\n return {\n status: \"skipped\",\n reason: \"Embedding 配置不完整;SQLite FTS 已在消息入库时即时更新。\",\n chunks: 0,\n vectors: 0,\n startedAt,\n finishedAt: new Date().toISOString(),\n };\n }\n\n const vectorStore = new SQLiteVectorStore(input.database);\n try {\n const stats = await indexMessageChunks({\n messages: new MessageRepository(input.database),\n embedding: createEmbeddingModel(input.config, input.secrets),\n store: vectorStore,\n limit: input.limit,\n });\n\n return {\n status: \"completed\",\n chunks: stats.chunks,\n vectors: stats.vectors,\n startedAt,\n finishedAt: new Date().toISOString(),\n };\n } finally {\n vectorStore.close();\n }\n}\n","import Fastify, { type FastifyInstance } from \"fastify\";\nimport { loadSecrets } from \"../config/store.js\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { openDatabase } from \"../db/database.js\";\nimport { FileJobRepository } from \"../files/jobs.js\";\nimport { getGatewayStatus } from \"../gateway/index.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { processMessagesNow } from \"../rag/manual-index.js\";\n\nfunction buildHtml(): string {\n return `<!doctype html>\n<html lang=\"zh-CN\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>ChatterCatcher</title>\n <style>\n :root {\n color-scheme: light;\n --bg: #f6f5f0;\n --panel: #ffffff;\n --text: #1f2933;\n --muted: #667085;\n --line: #d9d7cf;\n --accent: #1f7a5a;\n --warn: #9a5b13;\n }\n * { box-sizing: border-box; }\n body {\n margin: 0;\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: var(--bg);\n color: var(--text);\n }\n main { max-width: 1120px; margin: 0 auto; padding: 32px 24px 48px; overflow-x: hidden; }\n header {\n display: flex;\n justify-content: space-between;\n gap: 20px;\n align-items: flex-start;\n padding-bottom: 24px;\n border-bottom: 1px solid var(--line);\n }\n h1 { margin: 0; font-size: 30px; line-height: 1.1; letter-spacing: 0; }\n h2 { margin: 0 0 12px; font-size: 18px; letter-spacing: 0; }\n p { margin: 8px 0 0; color: var(--muted); }\n code { background: #eceae2; border-radius: 4px; padding: 2px 6px; }\n button {\n appearance: none;\n border: 1px solid var(--line);\n background: var(--panel);\n color: var(--text);\n border-radius: 6px;\n padding: 8px 12px;\n cursor: pointer;\n }\n button:hover { border-color: var(--accent); }\n .actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }\n .grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 12px;\n margin: 24px 0;\n }\n .metric {\n background: var(--panel);\n border: 1px solid var(--line);\n border-radius: 8px;\n padding: 16px;\n min-height: 112px;\n }\n .label { color: var(--muted); font-size: 13px; }\n .value { margin-top: 10px; font-size: 22px; font-weight: 650; overflow-wrap: anywhere; line-height: 1.18; }\n .note { margin-top: 8px; color: var(--muted); font-size: 13px; line-height: 1.45; }\n .layout {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(280px, 380px);\n gap: 24px;\n }\n .layout > * { min-width: 0; }\n section { padding: 20px 0; border-top: 1px solid var(--line); }\n section:first-child { border-top: 0; }\n .message-list { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }\n .message-item { padding: 14px 16px; border-bottom: 1px solid var(--line); }\n .message-item:last-child { border-bottom: 0; }\n .message-meta { display: flex; flex-wrap: wrap; gap: 8px 14px; color: var(--muted); font-size: 13px; line-height: 1.4; }\n .message-body { margin-top: 8px; white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.55; }\n table { width: 100%; table-layout: fixed; border-collapse: collapse; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }\n th, td { padding: 12px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; overflow: hidden; text-overflow: ellipsis; }\n th { color: var(--muted); font-size: 13px; font-weight: 600; }\n tr:last-child td { border-bottom: 0; }\n .message { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n .id-text, .path { display: block; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--muted); font-size: 13px; }\n .compact-table th:first-child, .compact-table td:first-child { width: 120px; }\n .compact-table th:nth-child(2), .compact-table td:nth-child(2) { width: 180px; }\n .status-ok { color: var(--accent); }\n .status-warn { color: var(--warn); }\n .empty { color: var(--muted); padding: 18px; background: var(--panel); border: 1px dashed var(--line); border-radius: 8px; }\n .status-line { margin-top: 10px; font-size: 13px; color: var(--muted); text-align: right; }\n @media (max-width: 900px) {\n header, .layout { display: block; }\n .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n header button { margin-top: 16px; }\n }\n @media (max-width: 560px) {\n main { padding: 24px 16px 36px; }\n .grid { grid-template-columns: 1fr; }\n }\n </style>\n </head>\n <body>\n <main>\n <header>\n <div>\n <h1>ChatterCatcher</h1>\n <p>本地优先的家庭群知识库。问答必须先检索 RAG 证据,不堆叠全量上下文。</p>\n </div>\n <div>\n <div class=\"actions\">\n <button id=\"process-messages\" type=\"button\">立即处理</button>\n </div>\n <div id=\"action-status\" class=\"status-line\"></div>\n </div>\n </header>\n\n <div class=\"grid\" id=\"metrics\"></div>\n\n <div class=\"layout\">\n <div>\n <section>\n <h2>最近消息</h2>\n <div id=\"messages\" class=\"empty\">正在读取...</div>\n </section>\n </div>\n <aside>\n <section>\n <h2>群聊</h2>\n <div id=\"chats\" class=\"empty\">正在读取...</div>\n </section>\n <section>\n <h2>文件库</h2>\n <div id=\"files\" class=\"empty\">正在读取...</div>\n </section>\n <section>\n <h2>解析任务</h2>\n <div id=\"file-jobs\" class=\"empty\">正在读取...</div>\n </section>\n <section>\n <h2>本地操作</h2>\n <p><code>chattercatcher settings</code> 修改配置。</p>\n <p><code>chattercatcher files add &lt;path...&gt;</code> 导入文本、DOCX 或 PDF 文件。</p>\n <p><code>chattercatcher doctor</code> 检查飞书、模型、RAG 和本地存储。</p>\n </section>\n </aside>\n </div>\n </main>\n <script>\n const metrics = document.querySelector(\"#metrics\");\n const messages = document.querySelector(\"#messages\");\n const chats = document.querySelector(\"#chats\");\n const files = document.querySelector(\"#files\");\n const fileJobs = document.querySelector(\"#file-jobs\");\n const processMessages = document.querySelector(\"#process-messages\");\n const actionStatus = document.querySelector(\"#action-status\");\n\n function fmt(value) {\n return value == null || value === \"\" ? \"-\" : String(value);\n }\n\n function escapeHtml(value) {\n return fmt(value)\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\");\n }\n\n function isOpaqueId(value) {\n return /^(ou|oc|om|cli|on|un|uid)_?[a-z0-9]+/i.test(fmt(value));\n }\n\n function formatDateTime(value) {\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return fmt(value);\n const pad = (input) => String(input).padStart(2, \"0\");\n return [\n date.getFullYear(),\n pad(date.getMonth() + 1),\n pad(date.getDate()),\n ].join(\"/\") + \" \" + [\n pad(date.getHours()),\n pad(date.getMinutes()),\n pad(date.getSeconds()),\n ].join(\":\");\n }\n\n function displaySender(value) {\n return isOpaqueId(value) ? \"群成员\" : fmt(value);\n }\n\n function displayChatName(value, platform) {\n if (!isOpaqueId(value)) return fmt(value);\n return platform === \"feishu\" ? \"飞书群聊\" : \"群聊\";\n }\n\n function formatGatewayValue(gateway) {\n if (gateway.connection === \"running\") return \"运行中\";\n if (!gateway.configured) return \"未配置\";\n return \"待启动\";\n }\n\n function formatGatewayNote(gateway) {\n if (gateway.connection === \"running\" && gateway.pid) return \"PID \" + gateway.pid;\n return \"飞书长连接\";\n }\n\n function renderMetrics(status) {\n const gatewayClass = status.gateway.configured ? \"status-ok\" : \"status-warn\";\n metrics.innerHTML = [\n [\"Gateway\", formatGatewayValue(status.gateway), formatGatewayNote(status.gateway), gatewayClass],\n [\"群聊\", status.data.chats, \"本地群聊数\", \"\"],\n [\"消息\", status.data.messages, \"已入库消息\", \"\"],\n [\"文件\", status.data.files, \"文件知识源\", \"\"],\n ].map(([label, value, note, extra]) => \\`\n <div class=\"metric\">\n <div class=\"label\">\\${escapeHtml(label)}</div>\n <div class=\"value \\${extra}\">\\${escapeHtml(value)}</div>\n <div class=\"note\">\\${escapeHtml(note)}</div>\n </div>\n \\`).join(\"\");\n }\n\n function renderMessages(items) {\n if (items.length === 0) {\n messages.className = \"empty\";\n messages.textContent = \"还没有消息。启动 Gateway 后,群聊文本会进入本地 RAG 索引。\";\n return;\n }\n messages.className = \"\";\n messages.innerHTML = \\`\n <div class=\"message-list\">\n \\${items.map((item) => \\`\n <article class=\"message-item\">\n <div class=\"message-meta\">\n <span>\\${escapeHtml(formatDateTime(item.sentAt))}</span>\n <span>\\${escapeHtml(displaySender(item.senderName))}</span>\n <span>\\${escapeHtml(displayChatName(item.chatName, item.platform))}</span>\n </div>\n <div class=\"message-body\">\\${escapeHtml(item.text)}</div>\n </article>\n \\`).join(\"\")}\n </div>\n \\`;\n }\n\n function renderChats(items) {\n if (items.length === 0) {\n chats.className = \"empty\";\n chats.textContent = \"还没有群聊记录。\";\n return;\n }\n chats.className = \"\";\n chats.innerHTML = \\`\n <table>\n <thead><tr><th>名称</th><th>平台</th></tr></thead>\n <tbody>\n \\${items.map((item) => \\`\n <tr>\n <td><span class=\"id-text\" title=\"\\${escapeHtml(item.name)}\">\\${escapeHtml(displayChatName(item.name, item.platform))}</span></td>\n <td>\\${escapeHtml(item.platform)}</td>\n </tr>\n \\`).join(\"\")}\n </tbody>\n </table>\n \\`;\n }\n\n function renderFiles(items) {\n if (items.length === 0) {\n files.className = \"empty\";\n files.textContent = \"还没有文件。可先运行 chattercatcher files add <path...> 导入文本、DOCX 或 PDF 文件。\";\n return;\n }\n files.className = \"\";\n files.innerHTML = \\`\n <table>\n <thead><tr><th>文件</th><th>解析器</th><th>字符</th></tr></thead>\n <tbody>\n \\${items.map((item) => \\`\n <tr>\n <td>\n <div>\\${escapeHtml(item.fileName)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.storedPath)}\">\\${escapeHtml(item.storedPath)}</div>\n </td>\n <td>\\${escapeHtml(item.parser || \"unknown\")}</td>\n <td>\\${escapeHtml(item.characters)}</td>\n </tr>\n \\`).join(\"\")}\n </tbody>\n </table>\n \\`;\n }\n\n function renderFileJobs(items) {\n if (items.length === 0) {\n fileJobs.className = \"empty\";\n fileJobs.textContent = \"还没有文件解析任务。\";\n return;\n }\n fileJobs.className = \"\";\n fileJobs.innerHTML = \\`\n <table>\n <thead><tr><th>任务</th><th>状态</th></tr></thead>\n <tbody>\n \\${items.map((item) => \\`\n <tr>\n <td>\n <div>\\${escapeHtml(item.fileName)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.id)}\">ID: \\${escapeHtml(item.id)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.error || item.storedPath)}\">\\${escapeHtml(item.error || item.storedPath)}</div>\n </td>\n <td>\\${escapeHtml(item.status)}</td>\n </tr>\n \\`).join(\"\")}\n </tbody>\n </table>\n \\`;\n }\n\n async function load() {\n const [status, recent, chatList, fileList, jobList] = await Promise.all([\n fetch(\"/api/status\").then((response) => response.json()),\n fetch(\"/api/messages/recent?limit=20\").then((response) => response.json()),\n fetch(\"/api/chats\").then((response) => response.json()),\n fetch(\"/api/files\").then((response) => response.json()),\n fetch(\"/api/file-jobs\").then((response) => response.json()),\n ]);\n renderMetrics(status);\n renderMessages(recent.items);\n renderChats(chatList.items);\n renderFiles(fileList.items);\n renderFileJobs(jobList.items);\n }\n\n async function processNow() {\n processMessages.disabled = true;\n actionStatus.textContent = \"正在处理消息索引...\";\n try {\n const response = await fetch(\"/api/process/messages\", { method: \"POST\" });\n const result = await response.json();\n if (!response.ok) {\n actionStatus.textContent = result.message || \"处理失败。\";\n return;\n }\n\n if (result.status === \"skipped\") {\n actionStatus.textContent = result.reason;\n } else {\n actionStatus.textContent = \\`处理完成:chunks=\\${result.chunks}, vectors=\\${result.vectors}\\`;\n }\n await load();\n } catch (error) {\n actionStatus.textContent = error instanceof Error ? error.message : String(error);\n } finally {\n processMessages.disabled = false;\n }\n }\n\n processMessages.addEventListener(\"click\", () => void processNow());\n void load();\n setInterval(() => {\n if (document.visibilityState === \"visible\") {\n void load();\n }\n }, 5000);\n </script>\n </body>\n</html>`;\n}\n\nfunction parseLimit(value: string | undefined, fallback: number, max: number): number {\n const rawLimit = Number(value ?? fallback);\n return Number.isFinite(rawLimit) ? Math.min(Math.max(Math.trunc(rawLimit), 1), max) : fallback;\n}\n\nexport function createWebApp(config: AppConfig): FastifyInstance {\n const app = Fastify({ logger: false });\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const fileJobs = new FileJobRepository(database);\n\n app.addHook(\"onClose\", async () => {\n database.close();\n });\n\n app.get(\"/api/status\", async () => ({\n app: \"ChatterCatcher\",\n gateway: getGatewayStatus(config),\n data: {\n chats: messages.getChatCount(),\n messages: messages.getMessageCount(),\n files: messages.listFiles(1_000).length,\n },\n rag: {\n mode: \"required\",\n note: \"问答必须先检索证据,禁止全量上下文堆叠。\",\n retrieval: {\n keyword: \"SQLite FTS5\",\n vector: \"SQLite Vector\",\n hybrid: true,\n },\n },\n web: config.web,\n }));\n\n app.get(\"/api/chats\", async () => ({\n items: messages.listChats(),\n }));\n\n app.get(\"/api/files\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 50, 200);\n return {\n items: messages.listFiles(limit),\n };\n });\n\n app.get(\"/api/file-jobs\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 50, 200);\n const status = (request.query as { status?: string }).status;\n return {\n items: fileJobs.list(limit, status === \"processing\" || status === \"indexed\" || status === \"failed\" ? { status } : {}),\n };\n });\n\n app.get(\"/api/messages/recent\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 20, 100);\n return {\n items: messages.listRecentMessages(limit),\n };\n });\n\n app.post(\"/api/process/messages\", async (_request, reply) => {\n try {\n return await processMessagesNow({\n config,\n secrets: await loadSecrets(),\n database,\n limit: 10_000,\n });\n } catch (error) {\n reply.code(500);\n return {\n status: \"failed\",\n message: error instanceof Error ? error.message : String(error),\n };\n }\n });\n\n app.get(\"/\", async (_request, reply) => {\n reply.type(\"text/html; charset=utf-8\");\n return buildHtml();\n });\n\n return app;\n}\n\nexport async function startWebServer(config: AppConfig): Promise<void> {\n const app = createWebApp(config);\n await app.listen({ host: config.web.host, port: config.web.port });\n const address = app.server.address();\n const url =\n typeof address === \"string\" ? address : `http://${config.web.host}:${address?.port ?? config.web.port}`;\n console.log(`ChatterCatcher Web UI: ${url}`);\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["fs","path","os","path","path","input","fs","path","path","fs","input","deleted","fs","path","path","fs","fs","path","input","fs","path","path","fs","data","crypto","nowIso","crypto","index","input","index","input","fs","fs","path","path","input","fs","fs","path","data","input","lark","input","index","input","input","data","lark","fs","path","input","path","fs","crypto","fs","path","fs","path","path","crypto","input","fs","asObject","fileKey","input","fs","path","path","fs","files","input","input","index","toEvidenceSource","input","files","fs"]}
1
+ {"version":3,"sources":["../src/config/paths.ts","../src/rag/lancedb-store.ts","../src/cli.ts","../package.json","../src/config/store.ts","../src/config/schema.ts","../src/config/update.ts","../src/data/deletion.ts","../src/db/database.ts","../src/doctor/checks.ts","../src/files/jobs.ts","../src/gateway/runtime.ts","../src/logs/reader.ts","../src/gateway/index.ts","../src/llm/openai-compatible.ts","../src/messages/repository.ts","../src/messages/chunker.ts","../src/rag/hybrid-retriever.ts","../src/rag/message-retriever.ts","../src/rag/vector-retriever.ts","../src/rag/factory.ts","../src/export/data-export.ts","../src/export/data-restore.ts","../src/feishu/gateway.ts","../src/rag/citations.ts","../src/rag/answer.ts","../src/rag/qa-service.ts","../src/feishu/question.ts","../src/feishu/sender.ts","../src/feishu/resource-downloader.ts","../src/files/ingest.ts","../src/files/parser.ts","../src/feishu/normalize.ts","../src/gateway/ingest.ts","../src/gateway/detached.ts","../src/rag/indexer.ts","../src/rag/manual-index.ts","../src/update/npm-updater.ts","../src/web/server.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\n\nexport function getChatterCatcherHome(): string {\n return process.env.CHATTERCATCHER_HOME || path.join(os.homedir(), \".chattercatcher\");\n}\n\nexport function resolveHomePath(value: string): string {\n if (value === \"~\") {\n return os.homedir();\n }\n\n if (value.startsWith(\"~/\") || value.startsWith(\"~\\\\\")) {\n return path.join(os.homedir(), value.slice(2));\n }\n\n return path.resolve(value);\n}\n\nexport function getConfigPath(): string {\n return path.join(getChatterCatcherHome(), \"config.json\");\n}\n\nexport function getSecretsPath(): string {\n return path.join(getChatterCatcherHome(), \"secrets.json\");\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport type { EvidenceBlock, EvidenceSource } from \"./types.js\";\nimport type { VectorRecord, VectorSearchResult, VectorStore } from \"./vector-store.js\";\n\ninterface LanceVectorRow {\n id: string;\n vector: number[];\n text: string;\n source_json: string;\n}\n\ninterface LanceVectorSearchRow extends LanceVectorRow {\n _distance?: number;\n}\n\ninterface LanceTable {\n delete(filter: string): Promise<unknown>;\n add(data: Record<string, unknown>[]): Promise<unknown>;\n vectorSearch(vector: number[]): {\n limit(limit: number): {\n toArray(): Promise<unknown[]>;\n };\n };\n countRows(): Promise<number>;\n}\n\ninterface LanceConnection {\n close(): void;\n tableNames(): Promise<string[]>;\n openTable(name: string): Promise<LanceTable>;\n createTable(name: string, data: Record<string, unknown>[]): Promise<LanceTable>;\n}\n\nconst DEFAULT_TABLE_NAME = \"message_chunks\";\n\nexport function getLanceDbPath(config: AppConfig): string {\n return path.join(resolveHomePath(config.storage.dataDir), \"vector\", \"lancedb\");\n}\n\nfunction toRow(record: VectorRecord): LanceVectorRow {\n return {\n id: record.id,\n vector: record.vector,\n text: record.evidence.text,\n source_json: JSON.stringify(record.evidence.source),\n };\n}\n\nfunction toLanceData(rows: LanceVectorRow[]): Record<string, unknown>[] {\n return rows.map((row) => ({\n id: row.id,\n vector: row.vector,\n text: row.text,\n source_json: row.source_json,\n }));\n}\n\nfunction escapeSqlString(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nfunction toEvidence(row: LanceVectorSearchRow): VectorSearchResult {\n const distance = row._distance ?? 0;\n const vectorScore = 1 / (1 + Math.max(0, distance));\n\n return {\n id: row.id,\n text: row.text,\n score: vectorScore,\n vectorScore,\n source: JSON.parse(row.source_json) as EvidenceSource,\n };\n}\n\nexport class LanceDbVectorStore implements VectorStore {\n private constructor(\n private readonly connection: LanceConnection,\n private readonly tableName: string,\n ) {}\n\n static async connect(uri: string, tableName = DEFAULT_TABLE_NAME): Promise<LanceDbVectorStore> {\n await fs.mkdir(uri, { recursive: true });\n const lancedb = await import(\"@lancedb/lancedb\");\n const connection = (await lancedb.connect(uri)) as LanceConnection;\n return new LanceDbVectorStore(connection, tableName);\n }\n\n static async connectFromConfig(config: AppConfig, tableName = DEFAULT_TABLE_NAME): Promise<LanceDbVectorStore> {\n return LanceDbVectorStore.connect(getLanceDbPath(config), tableName);\n }\n\n close(): void {\n this.connection.close();\n }\n\n async upsert(records: VectorRecord[]): Promise<void> {\n if (records.length === 0) {\n return;\n }\n\n const rows = records.map(toRow);\n const data = toLanceData(rows);\n const table = await this.ensureTable(data);\n const ids = rows.map((row) => `'${escapeSqlString(row.id)}'`).join(\", \");\n await table.delete(`id IN (${ids})`);\n await table.add(data);\n }\n\n async search(vector: number[], limit: number): Promise<VectorSearchResult[]> {\n const table = await this.openTableIfExists();\n if (!table) {\n return [];\n }\n\n const rows = (await table.vectorSearch(vector).limit(limit).toArray()) as LanceVectorSearchRow[];\n return rows.map(toEvidence);\n }\n\n async count(): Promise<number> {\n const table = await this.openTableIfExists();\n if (!table) {\n return 0;\n }\n\n return table.countRows();\n }\n\n private async ensureTable(initialRows: Record<string, unknown>[]): Promise<LanceTable> {\n const table = await this.openTableIfExists();\n if (table) {\n return table;\n }\n\n return this.connection.createTable(this.tableName, initialRows);\n }\n\n private async openTableIfExists(): Promise<LanceTable | null> {\n const tableNames = await this.connection.tableNames();\n if (!tableNames.includes(this.tableName)) {\n return null;\n }\n\n return this.connection.openTable(this.tableName);\n }\n}\n","#!/usr/bin/env node\nimport { input, password, select, confirm, number } from \"@inquirer/prompts\";\nimport { Command } from \"commander\";\nimport fs from \"node:fs/promises\";\nimport packageJson from \"../package.json\" with { type: \"json\" };\nimport type { AppConfig, AppSecrets } from \"./config/schema.js\";\nimport { loadConfig, loadSecrets, resetConfigFiles, saveConfig, saveSecrets, ensureConfigFiles, maskSecret } from \"./config/store.js\";\nimport { applySecretInput, resolveEmbeddingApiKey } from \"./config/update.js\";\nimport { getChatterCatcherHome, getConfigPath, getSecretsPath } from \"./config/paths.js\";\nimport { deleteLocalData, type DeleteTargetType } from \"./data/deletion.js\";\nimport { getDatabasePath, openDatabase } from \"./db/database.js\";\nimport { formatDoctorChecks, runDoctor } from \"./doctor/checks.js\";\nimport { exportLocalData } from \"./export/data-export.js\";\nimport { restoreLocalData } from \"./export/data-restore.js\";\nimport { createFeishuGateway } from \"./feishu/gateway.js\";\nimport type { FeishuReceiveMessageEvent } from \"./feishu/normalize.js\";\nimport { FeishuQuestionHandler } from \"./feishu/question.js\";\nimport { FeishuResourceDownloader } from \"./feishu/resource-downloader.js\";\nimport { FeishuMessageSender } from \"./feishu/sender.js\";\nimport { ingestLocalFile } from \"./files/ingest.js\";\nimport { FileJobRepository } from \"./files/jobs.js\";\nimport { GatewayIngestor } from \"./gateway/ingest.js\";\nimport { startDetachedGateway } from \"./gateway/detached.js\";\nimport { getGatewayStatus } from \"./gateway/index.js\";\nimport { getGatewayLogPath, removeGatewayPidRecord, stopGatewayProcess, writeGatewayPidRecord } from \"./gateway/runtime.js\";\nimport { createChatModel, createEmbeddingModel } from \"./llm/openai-compatible.js\";\nimport { followLogFile, getLogsDirectory, normalizeLineCount, readLatestLogTail } from \"./logs/reader.js\";\nimport { MessageRepository } from \"./messages/repository.js\";\nimport { createHybridRetriever, hasEmbeddingConfig } from \"./rag/factory.js\";\nimport { indexMessageChunks } from \"./rag/indexer.js\";\nimport { processMessagesNow } from \"./rag/manual-index.js\";\nimport { askWithRag } from \"./rag/qa-service.js\";\nimport { formatCitation } from \"./rag/citations.js\";\nimport { updateChatterCatcher } from \"./update/npm-updater.js\";\nimport { startWebServer } from \"./web/server.js\";\n\nconst program = new Command();\n\nasync function promptForConfiguration(config: AppConfig, secrets: AppSecrets): Promise<void> {\n config.feishu.domain = await select({\n message: \"选择飞书区域\",\n choices: [\n { name: \"飞书(中国)\", value: \"feishu\" as const },\n { name: \"Lark(国际)\", value: \"lark\" as const },\n ],\n default: config.feishu.domain,\n });\n config.feishu.appId = await input({ message: \"飞书 App ID\", default: config.feishu.appId });\n secrets.feishu.appSecret = applySecretInput(\n secrets.feishu.appSecret,\n await password({ message: secrets.feishu.appSecret ? \"飞书 App Secret(留空保留)\" : \"飞书 App Secret\", mask: \"*\" }),\n );\n\n config.llm.baseUrl = await input({ message: \"LLM Base URL(OpenAI-compatible)\", default: config.llm.baseUrl });\n secrets.llm.apiKey = applySecretInput(\n secrets.llm.apiKey,\n await password({ message: secrets.llm.apiKey ? \"LLM API Key(留空保留)\" : \"LLM API Key\", mask: \"*\" }),\n );\n config.llm.model = await input({ message: \"Chat Model\", default: config.llm.model });\n\n config.embedding.baseUrl = await input({\n message: \"Embedding Base URL(留空则使用 LLM Base URL)\",\n default: config.embedding.baseUrl || config.llm.baseUrl,\n });\n secrets.embedding.apiKey = resolveEmbeddingApiKey({\n currentEmbeddingKey: secrets.embedding.apiKey,\n nextEmbeddingKey: await password({\n message: secrets.embedding.apiKey ? \"Embedding API Key(留空保留)\" : \"Embedding API Key(留空则使用 LLM API Key)\",\n mask: \"*\",\n }),\n llmApiKey: secrets.llm.apiKey,\n });\n config.embedding.model = await input({ message: \"Embedding Model\", default: config.embedding.model });\n const dimension = await number({\n message: \"Embedding 维度(不知道可先留空)\",\n default: config.embedding.dimension ?? undefined,\n required: false,\n });\n config.embedding.dimension = dimension ?? null;\n\n config.web.port =\n (await number({ message: \"Web UI 端口\", default: config.web.port, required: true })) ?? config.web.port;\n config.feishu.requireMention = await confirm({\n message: \"群聊回答是否要求 @ChatterCatcher?\",\n default: config.feishu.requireMention,\n });\n}\n\nfunction printSettings(config: AppConfig, secrets: AppSecrets): void {\n console.log(JSON.stringify(\n {\n home: getChatterCatcherHome(),\n config,\n secrets: {\n feishu: { appSecret: maskSecret(secrets.feishu.appSecret) },\n llm: { apiKey: maskSecret(secrets.llm.apiKey) },\n embedding: { apiKey: maskSecret(secrets.embedding.apiKey) },\n },\n },\n null,\n 2,\n ));\n}\n\nprogram\n .name(\"chattercatcher\")\n .description(\"本地优先的飞书/Lark 家庭群知识机器人\")\n .version(packageJson.version);\n\nprogram.command(\"setup\").description(\"交互式初始化配置\").action(async () => {\n const { config, secrets } = await ensureConfigFiles();\n await promptForConfiguration(config, secrets);\n await saveConfig(config);\n await saveSecrets(secrets);\n console.log(`配置已保存:${getConfigPath()}`);\n console.log(`密钥已保存:${getSecretsPath()}`);\n});\n\nconst settings = program.command(\"settings\").description(\"查看或修改配置\");\n\nsettings.action(async () => {\n const { config, secrets } = await ensureConfigFiles();\n await promptForConfiguration(config, secrets);\n await saveConfig(config);\n await saveSecrets(secrets);\n console.log(`配置已更新:${getConfigPath()}`);\n console.log(`密钥已更新:${getSecretsPath()}`);\n});\n\nsettings.command(\"show\").description(\"查看当前配置(密钥脱敏)\").action(async () => {\n printSettings(await loadConfig(), await loadSecrets());\n});\n\nsettings.command(\"reset\").description(\"重置本地配置和密钥\").action(async () => {\n const shouldReset = await confirm({ message: \"确认重置 ChatterCatcher 配置?\", default: false });\n if (!shouldReset) {\n console.log(\"已取消。\");\n return;\n }\n\n await resetConfigFiles();\n console.log(\"配置已重置。\");\n});\n\nprogram.command(\"doctor\").description(\"检查本地配置、存储和可选在线连通性\").option(\"--online\", \"检查 LLM 和 Embedding 接口连通性\").action(async (options: { online?: boolean }) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const checks = await runDoctor(config, secrets, { online: options.online });\n console.log(formatDoctorChecks(checks));\n});\n\nprogram.command(\"update\").description(\"升级 ChatterCatcher 到 npm 最新版本\").option(\"--dry-run\", \"只检查并显示将执行的升级命令\").action(async (options: { dryRun?: boolean }) => {\n const result = await updateChatterCatcher({ currentVersion: packageJson.version, dryRun: options.dryRun });\n\n if (result.status === \"up-to-date\") {\n console.log(`ChatterCatcher 已是最新版本:${result.currentVersion}`);\n return;\n }\n\n if (result.status === \"dry-run\") {\n console.log(`当前版本:${result.currentVersion}`);\n console.log(`最新版本:${result.latestVersion}`);\n console.log(`将执行:${result.command}`);\n return;\n }\n\n if (result.status === \"updated\") {\n console.log(`升级完成:${result.currentVersion} -> ${result.latestVersion}`);\n console.log(\"请重新打开终端或重新运行 chattercatcher --version 确认版本。\");\n return;\n }\n\n if (result.status === \"query-failed\") {\n console.error(`无法获取最新版本:${result.error}`);\n process.exitCode = 1;\n return;\n }\n\n console.error(`升级失败:${result.error}`);\n console.error(`可手动运行:${result.command}`);\n process.exitCode = 1;\n});\n\nconst gateway = program.command(\"gateway\").description(\"管理本地飞书 Gateway\");\n\nasync function startGatewayForegroundCommand(): Promise<void> {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const status = getGatewayStatus(config, secrets);\n const pidRecordBase = {\n pid: process.pid,\n startedAt: new Date().toISOString(),\n command: process.argv.join(\" \"),\n logFile: getGatewayLogPath(),\n };\n\n if (!status.configured) {\n writeGatewayPidRecord(undefined, {\n ...pidRecordBase,\n mode: \"web\",\n });\n console.log(status.message);\n console.log(\"本地 Web UI 仍会启动,方便继续配置。\");\n await startWebServer(config);\n return;\n }\n\n writeGatewayPidRecord(undefined, {\n ...pidRecordBase,\n mode: \"gateway\",\n });\n\n const database = openDatabase(config);\n const { LanceDbVectorStore } = hasEmbeddingConfig(config, secrets)\n ? await import(\"./rag/lancedb-store.js\")\n : { LanceDbVectorStore: null };\n const vectorStore = LanceDbVectorStore ? await LanceDbVectorStore.connectFromConfig(config) : null;\n const gatewayRuntime = createFeishuGateway({\n config,\n secrets,\n ingestor: new GatewayIngestor(database),\n resourceDownloader: FeishuResourceDownloader.fromConfig(config, secrets),\n attachmentVectorIndexer: vectorStore\n ? (messageId) =>\n indexMessageChunks({\n messages: new MessageRepository(database),\n embedding: createEmbeddingModel(config, secrets),\n store: vectorStore,\n messageIds: [messageId],\n })\n : undefined,\n questionHandler: new FeishuQuestionHandler({\n config,\n secrets,\n database,\n sender: FeishuMessageSender.fromConfig(config, secrets),\n model: createChatModel(config, secrets),\n }),\n });\n\n const cleanup = () => {\n gatewayRuntime.stop();\n vectorStore?.close();\n database.close();\n removeGatewayPidRecord();\n };\n\n process.on(\"SIGINT\", () => {\n cleanup();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n cleanup();\n process.exit(0);\n });\n\n console.log(status.message);\n\n try {\n await gatewayRuntime.start();\n await startWebServer(config);\n } catch (error) {\n cleanup();\n throw error;\n }\n}\n\nasync function startGatewayCommand(options: { foreground?: boolean } = {}): Promise<void> {\n if (options.foreground) {\n await startGatewayForegroundCommand();\n return;\n }\n\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const result = startDetachedGateway({ config, secrets });\n\n console.log(result.message);\n if (result.pid) {\n console.log(`PID:${result.pid}`);\n }\n console.log(`日志文件:${result.logFile}`);\n console.log(\"查看日志:chattercatcher logs --follow --file gateway.log\");\n console.log(\"停止 Gateway:chattercatcher gateway stop\");\n}\n\ngateway.command(\"status\").description(\"查看 Gateway 状态\").action(async () => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n console.log(JSON.stringify(getGatewayStatus(config, secrets), null, 2));\n});\n\ngateway\n .command(\"start\")\n .description(\"启动飞书长连接 Gateway 和本地 Web UI\")\n .option(\"--foreground\", \"在当前终端以前台模式运行\")\n .action(startGatewayCommand);\n\ngateway.command(\"stop\").description(\"停止 Gateway\").action(() => {\n console.log(stopGatewayProcess().message);\n});\n\ngateway.command(\"restart\").description(\"重启 Gateway\").action(async () => {\n console.log(stopGatewayProcess().message);\n await startGatewayCommand();\n});\n\nconst web = program.command(\"web\").description(\"管理本地 Web UI\");\n\nweb.command(\"start\").description(\"启动本地 Web UI\").action(async () => {\n const config = await loadConfig();\n await startWebServer(config);\n});\n\nconst data = program.command(\"data\").description(\"管理本地知识库数据\");\n\nasync function deleteDataCommand(targetType: DeleteTargetType, targetId: string, options: { yes?: boolean }): Promise<void> {\n const shouldDelete =\n options.yes ||\n (await confirm({\n message: `确认删除 ${targetType}=${targetId} 的本地知识库记录?`,\n default: false,\n }));\n\n if (!shouldDelete) {\n console.log(\"已取消。\");\n return;\n }\n\n const config = await loadConfig();\n const database = openDatabase(config);\n try {\n const result = await deleteLocalData({ config, database, targetType, targetId });\n console.log(\n `删除完成:messages=${result.deletedMessages},chunks=${result.deletedChunks},fileJobs=${result.deletedFileJobs},chats=${result.deletedChats}`,\n );\n if (result.deletedStoredFiles.length > 0) {\n console.log(`已删除本地保存文件:${result.deletedStoredFiles.join(\";\")}`);\n }\n if (result.skippedStoredFiles.length > 0) {\n console.log(`跳过非数据目录文件:${result.skippedStoredFiles.join(\";\")}`);\n }\n console.log(\"SQLite FTS 已同步删除;如使用 LanceDB 语义检索,请运行 chattercatcher index rebuild。\");\n } finally {\n database.close();\n }\n}\n\nconst dataDelete = data.command(\"delete\").description(\"删除指定本地知识库数据\");\n\ndataDelete\n .command(\"message\")\n .description(\"按消息 ID 删除一条消息及其 RAG chunks\")\n .argument(\"<messageId>\", \"消息 ID\")\n .option(\"--yes\", \"跳过确认\")\n .action((messageId: string, options: { yes?: boolean }) => deleteDataCommand(\"message\", messageId, options));\n\ndataDelete\n .command(\"file\")\n .description(\"按文件消息 ID 删除文件知识源、解析任务和 dataDir 内保存文件\")\n .argument(\"<messageId>\", \"文件消息 ID\")\n .option(\"--yes\", \"跳过确认\")\n .action((messageId: string, options: { yes?: boolean }) => deleteDataCommand(\"file\", messageId, options));\n\ndataDelete\n .command(\"chat\")\n .description(\"按群聊 ID 删除该群聊下的消息和 RAG chunks\")\n .argument(\"<chatId>\", \"群聊 ID\")\n .option(\"--yes\", \"跳过确认\")\n .action((chatId: string, options: { yes?: boolean }) => deleteDataCommand(\"chat\", chatId, options));\n\nconst index = program.command(\"index\").description(\"管理 RAG 索引\");\n\nindex.command(\"status\").description(\"查看索引状态\").action(async () => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const { getLanceDbPath, LanceDbVectorStore } = await import(\"./rag/lancedb-store.js\");\n const vectorStore = await LanceDbVectorStore.connectFromConfig(config);\n const vectors = await vectorStore.count();\n console.log(JSON.stringify(\n {\n database: getDatabasePath(config),\n vectorDatabase: getLanceDbPath(config),\n chats: messages.getChatCount(),\n messages: messages.getMessageCount(),\n vectors,\n retrieval: {\n keyword: \"SQLite FTS5\",\n vector: hasEmbeddingConfig(config, secrets) ? \"LanceDB 已可用于语义检索\" : \"LanceDB 已接入;需配置 embedding 后启用语义检索\",\n hybrid: \"启用:SQLite FTS + LanceDB Vector\",\n rag: \"强制先检索证据再回答,禁止全量上下文堆叠\",\n },\n },\n null,\n 2,\n ));\n vectorStore.close();\n database.close();\n});\n\nindex.command(\"rebuild\").description(\"重建 LanceDB 向量索引\").option(\"--limit <number>\", \"最多索引的 chunk 数\", \"10000\").action(async (options: { limit: string }) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n\n if (!hasEmbeddingConfig(config, secrets)) {\n console.log(\"Embedding 配置不完整,无法重建向量索引。请运行 chattercatcher setup 或 chattercatcher settings。\");\n return;\n }\n\n const database = openDatabase(config);\n const { LanceDbVectorStore } = await import(\"./rag/lancedb-store.js\");\n const vectorStore = await LanceDbVectorStore.connectFromConfig(config);\n\n try {\n const stats = await indexMessageChunks({\n messages: new MessageRepository(database),\n embedding: createEmbeddingModel(config, secrets),\n store: vectorStore,\n limit: Number(options.limit),\n });\n console.log(`向量索引完成:chunks=${stats.chunks}, vectors=${stats.vectors}`);\n } finally {\n vectorStore.close();\n database.close();\n }\n});\n\nconst processCommand = program.command(\"process\").description(\"立即处理后台任务\");\n\nprocessCommand\n .command(\"messages\")\n .description(\"立即处理消息索引任务,把消息 chunks 写入 LanceDB 向量索引\")\n .option(\"--limit <number>\", \"最多处理的 chunk 数\", \"10000\")\n .action(async (options: { limit: string }) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n const limit = Number(options.limit);\n\n try {\n const result = await processMessagesNow({\n config,\n secrets,\n database,\n limit: Number.isFinite(limit) ? limit : 10000,\n });\n if (result.status === \"skipped\") {\n console.log(`处理跳过:${result.reason}`);\n return;\n }\n\n console.log(`消息处理完成:chunks=${result.chunks}, vectors=${result.vectors}`);\n } finally {\n database.close();\n }\n });\n\nconst files = program.command(\"files\").description(\"管理本地文件知识源\");\n\nfiles\n .command(\"add\")\n .description(\"把本地文件解析、保存到数据目录并写入 RAG 知识库\")\n .argument(\"<paths...>\", \"文件路径,支持 txt、md、json、csv、tsv、log、docx、pdf\")\n .action(async (paths: string[]) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const jobs = new FileJobRepository(database);\n\n try {\n for (const filePath of paths) {\n const result = await ingestLocalFile({ config, messages, jobs, filePath });\n console.log(\n `已导入文件:${result.fileName},解析器=${result.parser},字符数=${result.characters},消息ID=${result.messageId}`,\n );\n }\n console.log(\"文件已进入 SQLite FTS 检索;如已配置 embedding,可运行 chattercatcher index rebuild 更新 LanceDB 向量索引。\");\n } finally {\n database.close();\n }\n });\n\nfiles\n .command(\"jobs\")\n .description(\"查看文件解析任务状态\")\n .option(\"--limit <number>\", \"最多显示的任务数\", \"50\")\n .option(\"--status <status>\", \"按状态过滤:processing、indexed、failed\")\n .action(async (options: { limit: string; status?: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const limit = Number(options.limit);\n\n try {\n const status =\n options.status === \"processing\" || options.status === \"indexed\" || options.status === \"failed\"\n ? options.status\n : undefined;\n const jobs = new FileJobRepository(database).list(Number.isFinite(limit) ? limit : 50, { status });\n if (jobs.length === 0) {\n console.log(\"还没有文件解析任务。\");\n return;\n }\n\n for (const job of jobs) {\n console.log(\n `${job.fileName} | ID=${job.id} | 状态=${job.status} | 解析器=${job.parser ?? \"-\"} | 更新时间=${job.updatedAt}`,\n );\n if (job.error) {\n console.log(` 错误:${job.error}`);\n }\n if (job.storedPath) {\n console.log(` 本地保存:${job.storedPath}`);\n }\n }\n } finally {\n database.close();\n }\n });\n\nfiles\n .command(\"retry\")\n .description(\"重试一个失败的文件解析任务\")\n .argument(\"<jobId>\", \"文件解析任务 ID\")\n .action(async (jobId: string) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const jobs = new FileJobRepository(database);\n const messages = new MessageRepository(database);\n\n try {\n const job = jobs.get(jobId);\n if (!job) {\n console.log(`没有找到文件解析任务:${jobId}`);\n return;\n }\n\n const result = await ingestLocalFile({\n config,\n messages,\n jobs,\n filePath: job.sourcePath,\n });\n console.log(\n `重试完成:${result.fileName},状态=indexed,解析器=${result.parser},消息ID=${result.messageId}`,\n );\n } finally {\n database.close();\n }\n });\n\nfiles\n .command(\"list\")\n .description(\"查看已进入 RAG 的本地文件\")\n .option(\"--limit <number>\", \"最多显示的文件数\", \"50\")\n .action(async (options: { limit: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const limit = Number(options.limit);\n\n try {\n const files = messages.listFiles(Number.isFinite(limit) ? limit : 50);\n if (files.length === 0) {\n console.log(\"还没有文件。可运行 chattercatcher files add <path...> 导入文件。\");\n return;\n }\n\n for (const file of files) {\n console.log(\n `${file.fileName} | 解析器=${file.parser ?? \"unknown\"} | 字符数=${file.characters} | 导入时间=${file.importedAt}`,\n );\n if (file.parserWarnings?.length) {\n console.log(` 解析警告:${file.parserWarnings.join(\";\")}`);\n }\n if (file.storedPath) {\n console.log(` 本地保存:${file.storedPath}`);\n }\n }\n } finally {\n database.close();\n }\n });\n\nprogram.command(\"logs\").description(\"查看本地日志\").option(\"--follow\", \"持续输出日志\").option(\"--lines <number>\", \"显示末尾行数\", \"200\").option(\"--file <name>\", \"指定日志文件名或绝对路径\").action(async (options: { follow?: boolean; lines?: string; file?: string }) => {\n const result = await readLatestLogTail({\n fileName: options.file,\n lines: normalizeLineCount(options.lines),\n });\n\n if (!result) {\n console.log(`还没有日志文件:${getLogsDirectory()}`);\n return;\n }\n\n console.log(`日志文件:${result.file.path}`);\n if (result.content) {\n console.log(result.content);\n }\n\n if (!options.follow) {\n return;\n }\n\n const stop = await followLogFile({\n filePath: result.file.path,\n onChunk: (chunk) => process.stdout.write(chunk),\n onError: (error) => console.error(`日志跟随失败:${error.message}`),\n });\n\n const shutdown = () => {\n stop();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n await new Promise(() => undefined);\n});\n\nprogram.command(\"export\").description(\"导出本地知识库数据(不包含密钥)\").option(\"--out <path>\", \"导出 JSON 文件路径\").action(async (options: { out?: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n\n try {\n const result = await exportLocalData({ config, database, outputPath: options.out });\n console.log(`导出完成:${result.outputPath}`);\n console.log(`包含:群聊=${result.chats},消息=${result.messages},chunks=${result.chunks},文件任务=${result.fileJobs}`);\n } finally {\n database.close();\n }\n});\n\nprogram.command(\"restore\").description(\"从 ChatterCatcher 导出文件恢复本地知识库数据\").argument(\"<file>\", \"导出的 JSON 文件\").option(\"--replace\", \"先清空当前本地知识库,再恢复\").action(async (file: string, options: { replace?: boolean }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n\n try {\n const result = await restoreLocalData({ database, inputPath: file, replace: options.replace });\n console.log(`恢复完成:${result.inputPath}`);\n console.log(`模式:${result.mode === \"replace\" ? \"替换\" : \"合并\"}`);\n console.log(`包含:群聊=${result.chats},消息=${result.messages},chunks=${result.chunks},文件任务=${result.fileJobs}`);\n console.log(\"SQLite FTS 已重建;如使用 LanceDB 语义检索,请运行 chattercatcher index rebuild。\");\n } finally {\n database.close();\n }\n});\n\nconst dev = program.command(\"dev\").description(\"开发调试命令\");\n\ndev\n .command(\"ingest-message\")\n .description(\"写入一条本地测试消息\")\n .requiredOption(\"--text <text>\", \"消息文本\")\n .option(\"--chat <name>\", \"群名\", \"家庭群\")\n .option(\"--sender <name>\", \"发送人\", \"测试用户\")\n .action(async (options: { text: string; chat: string; sender: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const now = new Date().toISOString();\n const id = messages.ingest({\n platform: \"dev\",\n platformChatId: options.chat,\n chatName: options.chat,\n platformMessageId: `dev-${Date.now()}`,\n senderId: options.sender,\n senderName: options.sender,\n messageType: \"text\",\n text: options.text,\n sentAt: now,\n rawPayload: { dev: true },\n });\n\n console.log(`已写入消息:${id}`);\n database.close();\n });\n\ndev\n .command(\"ingest-feishu-event\")\n .description(\"从 JSON 文件模拟写入一条飞书消息事件\")\n .requiredOption(\"--file <path>\", \"飞书事件 JSON 文件\")\n .action(async (options: { file: string }) => {\n const config = await loadConfig();\n const database = openDatabase(config);\n\n try {\n const raw = await fs.readFile(options.file, \"utf8\");\n const payload = JSON.parse(raw) as FeishuReceiveMessageEvent;\n const result = new GatewayIngestor(database).ingestFeishuEvent(payload);\n\n if (!result.accepted) {\n console.log(result.reason);\n return;\n }\n\n console.log(`已写入飞书消息:${result.messageId}`);\n } finally {\n database.close();\n }\n });\n\ndev\n .command(\"search\")\n .description(\"通过本地 FTS 检索测试 RAG 证据\")\n .argument(\"<question>\", \"检索问题\")\n .action(async (question: string) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n const { retriever, close } = await createHybridRetriever({\n config,\n secrets,\n messages: new MessageRepository(database),\n });\n const evidence = await retriever.retrieve(question);\n\n if (evidence.length === 0) {\n console.log(\"没有检索到证据。\");\n close();\n database.close();\n return;\n }\n\n console.log(JSON.stringify(evidence, null, 2));\n close();\n database.close();\n });\n\ndev\n .command(\"ask\")\n .description(\"通过本地检索证据调用 LLM 回答\")\n .argument(\"<question>\", \"问题\")\n .action(async (question: string) => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n const { retriever, close } = await createHybridRetriever({\n config,\n secrets,\n messages: new MessageRepository(database),\n });\n\n try {\n const result = await askWithRag({\n question,\n retriever,\n model: createChatModel(config, secrets),\n });\n\n console.log(result.answer);\n if (result.citations.length > 0) {\n console.log(\"\\n引用:\");\n for (const citation of result.citations) {\n console.log(`- ${formatCitation(citation)}`);\n }\n }\n } finally {\n close();\n database.close();\n }\n });\n\nprogram.parseAsync().catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n});\n","{\n \"name\": \"chattercatcher\",\n \"version\": \"0.1.8\",\n \"description\": \"本地优先的飞书/Lark 家庭群知识库机器人\",\n \"type\": \"module\",\n \"main\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"homepage\": \"https://github.com/FlashingChen2024/chattercatcher#readme\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/FlashingChen2024/chattercatcher.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/FlashingChen2024/chattercatcher/issues\"\n },\n \"bin\": {\n \"chattercatcher\": \"dist/cli.js\"\n },\n \"files\": [\n \"assets\",\n \"dist\",\n \"docs\",\n \"README.md\",\n \"AGENTS.md\"\n ],\n \"directories\": {\n \"doc\": \"docs\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx src/cli.ts\",\n \"lint\": \"tsc --noEmit\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\"\n },\n \"keywords\": [\n \"feishu\",\n \"lark\",\n \"rag\",\n \"local-first\",\n \"knowledge-base\"\n ],\n \"author\": \"FlashingChen2024\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"@inquirer/prompts\": \"^8.4.2\",\n \"@lancedb/lancedb\": \"0.23.0\",\n \"@larksuiteoapi/node-sdk\": \"^1.62.0\",\n \"better-sqlite3\": \"^12.9.0\",\n \"commander\": \"^14.0.3\",\n \"fastify\": \"^5.8.5\",\n \"mammoth\": \"^1.12.0\",\n \"pdf-parse\": \"^2.4.5\",\n \"pino\": \"^10.3.1\",\n \"zod\": \"^4.3.6\"\n },\n \"devDependencies\": {\n \"@types/better-sqlite3\": \"^7.6.13\",\n \"@types/node\": \"^25.6.0\",\n \"@types/yazl\": \"^3.3.1\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^6.0.3\",\n \"vitest\": \"^4.1.5\",\n \"yazl\": \"^3.3.1\"\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n type AppConfig,\n type AppSecrets,\n appConfigSchema,\n appSecretsSchema,\n createDefaultConfig,\n createDefaultSecrets,\n} from \"./schema.js\";\nimport { getChatterCatcherHome, getConfigPath, getSecretsPath } from \"./paths.js\";\n\nasync function readJsonFile<T>(filePath: string, fallback: T): Promise<T> {\n try {\n const raw = await fs.readFile(filePath, \"utf8\");\n return JSON.parse(raw) as T;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return fallback;\n }\n\n throw error;\n }\n}\n\nasync function writeJsonFile(filePath: string, value: unknown): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nexport async function loadConfig(): Promise<AppConfig> {\n const raw = await readJsonFile(getConfigPath(), createDefaultConfig());\n return appConfigSchema.parse(raw);\n}\n\nexport async function saveConfig(config: AppConfig): Promise<void> {\n await writeJsonFile(getConfigPath(), appConfigSchema.parse(config));\n}\n\nexport async function loadSecrets(): Promise<AppSecrets> {\n const raw = await readJsonFile(getSecretsPath(), createDefaultSecrets());\n return appSecretsSchema.parse(raw);\n}\n\nexport async function saveSecrets(secrets: AppSecrets): Promise<void> {\n await writeJsonFile(getSecretsPath(), appSecretsSchema.parse(secrets));\n}\n\nexport async function ensureConfigFiles(): Promise<{ config: AppConfig; secrets: AppSecrets }> {\n await fs.mkdir(getChatterCatcherHome(), { recursive: true });\n const config = await loadConfig();\n const secrets = await loadSecrets();\n await saveConfig(config);\n await saveSecrets(secrets);\n return { config, secrets };\n}\n\nexport async function resetConfigFiles(): Promise<void> {\n await saveConfig(createDefaultConfig());\n await saveSecrets(createDefaultSecrets());\n}\n\nexport function maskSecret(value: string): string {\n if (!value) {\n return \"\";\n }\n\n if (value.length <= 8) {\n return \"********\";\n }\n\n return `${value.slice(0, 4)}...${value.slice(-4)}`;\n}\n","import os from \"node:os\";\nimport path from \"node:path\";\nimport { z } from \"zod\";\n\nfunction defaultDataDir(): string {\n return path.join(process.env.CHATTERCATCHER_HOME || path.join(os.homedir(), \".chattercatcher\"), \"data\");\n}\n\nexport const appConfigSchema = z.object({\n feishu: z.object({\n domain: z.enum([\"feishu\", \"lark\"]).default(\"feishu\"),\n appId: z.string().default(\"\"),\n groupPolicy: z.enum([\"open\", \"allowlist\", \"disabled\"]).default(\"open\"),\n requireMention: z.boolean().default(true),\n }),\n llm: z.object({\n baseUrl: z.string().url().or(z.literal(\"\")).default(\"\"),\n model: z.string().default(\"\"),\n }),\n embedding: z.object({\n baseUrl: z.string().url().or(z.literal(\"\")).default(\"\"),\n model: z.string().default(\"\"),\n dimension: z.number().int().positive().nullable().default(null),\n }),\n storage: z.object({\n dataDir: z.string().default(defaultDataDir),\n }),\n web: z.object({\n host: z.string().default(\"127.0.0.1\"),\n port: z.number().int().min(1).max(65535).default(3878),\n }),\n schedules: z.object({\n indexing: z.string().default(\"*/10 * * * *\"),\n }),\n});\n\nexport const appSecretsSchema = z.object({\n feishu: z.object({\n appSecret: z.string().default(\"\"),\n }),\n llm: z.object({\n apiKey: z.string().default(\"\"),\n }),\n embedding: z.object({\n apiKey: z.string().default(\"\"),\n }),\n});\n\nexport type AppConfig = z.infer<typeof appConfigSchema>;\nexport type AppSecrets = z.infer<typeof appSecretsSchema>;\n\nexport function createDefaultConfig(): AppConfig {\n return appConfigSchema.parse({\n feishu: {},\n llm: {},\n embedding: {},\n storage: {},\n web: {},\n schedules: {},\n });\n}\n\nexport function createDefaultSecrets(): AppSecrets {\n return appSecretsSchema.parse({\n feishu: {},\n llm: {},\n embedding: {},\n });\n}\n","export function applySecretInput(currentValue: string, nextValue: string | undefined): string {\n const trimmed = nextValue?.trim() ?? \"\";\n return trimmed ? trimmed : currentValue;\n}\n\nexport function resolveEmbeddingApiKey(input: {\n currentEmbeddingKey: string;\n nextEmbeddingKey?: string;\n llmApiKey: string;\n}): string {\n const explicit = applySecretInput(input.currentEmbeddingKey, input.nextEmbeddingKey);\n return explicit || input.llmApiKey;\n}\n\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport type DeleteTargetType = \"message\" | \"chat\" | \"file\";\n\nexport interface DeleteLocalDataResult {\n targetType: DeleteTargetType;\n targetId: string;\n deletedMessages: number;\n deletedChunks: number;\n deletedFileJobs: number;\n deletedChats: number;\n deletedStoredFiles: string[];\n skippedStoredFiles: string[];\n}\n\ninterface StoredPathRow {\n storedPath: string | null;\n}\n\nfunction emptyResult(targetType: DeleteTargetType, targetId: string): DeleteLocalDataResult {\n return {\n targetType,\n targetId,\n deletedMessages: 0,\n deletedChunks: 0,\n deletedFileJobs: 0,\n deletedChats: 0,\n deletedStoredFiles: [],\n skippedStoredFiles: [],\n };\n}\n\nfunction parseStoredPathFromRawPayload(rawPayloadJson: string): string | null {\n try {\n const parsed = JSON.parse(rawPayloadJson) as unknown;\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return null;\n }\n\n const storedPath = (parsed as { storedPath?: unknown }).storedPath;\n return typeof storedPath === \"string\" ? storedPath : null;\n } catch {\n return null;\n }\n}\n\nfunction isInsideDirectory(filePath: string, directory: string): boolean {\n const relative = path.relative(path.resolve(directory), path.resolve(filePath));\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n\nasync function removeStoredFiles(config: AppConfig, paths: string[]): Promise<{\n deleted: string[];\n skipped: string[];\n}> {\n const dataDir = resolveHomePath(config.storage.dataDir);\n const deleted: string[] = [];\n const skipped: string[] = [];\n const uniquePaths = [...new Set(paths.filter(Boolean).map((item) => path.resolve(item)))];\n\n for (const storedPath of uniquePaths) {\n if (!isInsideDirectory(storedPath, dataDir)) {\n skipped.push(storedPath);\n continue;\n }\n\n try {\n await fs.rm(storedPath, { force: true });\n deleted.push(storedPath);\n } catch {\n skipped.push(storedPath);\n }\n }\n\n return { deleted, skipped };\n}\n\nfunction getStoredPathsForMessages(database: SqliteDatabase, messageIds: string[]): string[] {\n if (messageIds.length === 0) {\n return [];\n }\n\n const rows = database\n .prepare(\n `\n SELECT raw_payload_json AS rawPayloadJson\n FROM messages\n WHERE id IN (${messageIds.map(() => \"?\").join(\", \")})\n `,\n )\n .all(...messageIds) as Array<{ rawPayloadJson: string }>;\n\n const fileJobRows = database\n .prepare(\n `\n SELECT stored_path AS storedPath\n FROM file_jobs\n WHERE message_id IN (${messageIds.map(() => \"?\").join(\", \")})\n `,\n )\n .all(...messageIds) as StoredPathRow[];\n\n return [\n ...rows.map((row) => parseStoredPathFromRawPayload(row.rawPayloadJson)).filter((item): item is string => Boolean(item)),\n ...fileJobRows.map((row) => row.storedPath).filter((item): item is string => Boolean(item)),\n ];\n}\n\nfunction deleteMessagesByIds(database: SqliteDatabase, messageIds: string[]): Omit<\n DeleteLocalDataResult,\n \"targetType\" | \"targetId\" | \"deletedStoredFiles\" | \"skippedStoredFiles\" | \"deletedChats\"\n> {\n if (messageIds.length === 0) {\n return {\n deletedMessages: 0,\n deletedChunks: 0,\n deletedFileJobs: 0,\n };\n }\n\n const placeholders = messageIds.map(() => \"?\").join(\", \");\n const deletedChunks = (database.prepare(`SELECT COUNT(*) AS count FROM message_chunks WHERE message_id IN (${placeholders})`).get(...messageIds) as { count: number }).count;\n const deletedFileJobs = database.prepare(`DELETE FROM file_jobs WHERE message_id IN (${placeholders})`).run(...messageIds).changes;\n database.prepare(`DELETE FROM message_chunks_fts WHERE message_id IN (${placeholders})`).run(...messageIds);\n const deletedMessages = database.prepare(`DELETE FROM messages WHERE id IN (${placeholders})`).run(...messageIds).changes;\n\n return {\n deletedMessages,\n deletedChunks,\n deletedFileJobs,\n };\n}\n\nexport async function deleteLocalData(input: {\n config: AppConfig;\n database: SqliteDatabase;\n targetType: DeleteTargetType;\n targetId: string;\n}): Promise<DeleteLocalDataResult> {\n const result = emptyResult(input.targetType, input.targetId);\n let storedPaths: string[] = [];\n\n const transaction = input.database.transaction(() => {\n if (input.targetType === \"chat\") {\n const messageIds = (\n input.database.prepare(\"SELECT id FROM messages WHERE chat_id = ?\").all(input.targetId) as Array<{ id: string }>\n ).map((row) => row.id);\n storedPaths = getStoredPathsForMessages(input.database, messageIds);\n const deleted = deleteMessagesByIds(input.database, messageIds);\n result.deletedMessages = deleted.deletedMessages;\n result.deletedChunks = deleted.deletedChunks;\n result.deletedFileJobs = deleted.deletedFileJobs;\n result.deletedChats = input.database.prepare(\"DELETE FROM chats WHERE id = ?\").run(input.targetId).changes;\n return;\n }\n\n if (input.targetType === \"file\") {\n const file = input.database\n .prepare(\"SELECT id FROM messages WHERE id = ? AND message_type = 'file'\")\n .get(input.targetId) as { id: string } | undefined;\n if (!file) {\n return;\n }\n }\n\n storedPaths = getStoredPathsForMessages(input.database, [input.targetId]);\n const deleted = deleteMessagesByIds(input.database, [input.targetId]);\n result.deletedMessages = deleted.deletedMessages;\n result.deletedChunks = deleted.deletedChunks;\n result.deletedFileJobs = deleted.deletedFileJobs;\n });\n\n transaction();\n\n const removed = await removeStoredFiles(input.config, storedPaths);\n result.deletedStoredFiles = removed.deleted;\n result.skippedStoredFiles = removed.skipped;\n return result;\n}\n","import Database from \"better-sqlite3\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\n\nexport type SqliteDatabase = Database.Database;\n\nexport function getDatabasePath(config: AppConfig): string {\n return path.join(resolveHomePath(config.storage.dataDir), \"chattercatcher.db\");\n}\n\nexport function openDatabase(config: AppConfig): SqliteDatabase {\n const databasePath = getDatabasePath(config);\n fs.mkdirSync(path.dirname(databasePath), { recursive: true });\n\n const database = new Database(databasePath);\n database.pragma(\"journal_mode = WAL\");\n database.pragma(\"foreign_keys = ON\");\n migrateDatabase(database);\n return database;\n}\n\nexport function migrateDatabase(database: SqliteDatabase): void {\n database.exec(`\n CREATE TABLE IF NOT EXISTS chats (\n id TEXT PRIMARY KEY,\n platform TEXT NOT NULL,\n platform_chat_id TEXT NOT NULL,\n name TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE(platform, platform_chat_id)\n );\n\n CREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n platform TEXT NOT NULL,\n platform_message_id TEXT NOT NULL,\n chat_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,\n sender_id TEXT NOT NULL,\n sender_name TEXT NOT NULL,\n message_type TEXT NOT NULL,\n text TEXT NOT NULL,\n raw_payload_json TEXT NOT NULL,\n sent_at TEXT NOT NULL,\n received_at TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE(platform, platform_message_id)\n );\n\n CREATE TABLE IF NOT EXISTS message_chunks (\n id TEXT PRIMARY KEY,\n message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n chunk_index INTEGER NOT NULL,\n text TEXT NOT NULL,\n metadata_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE(message_id, chunk_index)\n );\n\n CREATE VIRTUAL TABLE IF NOT EXISTS message_chunks_fts USING fts5(\n text,\n chunk_id UNINDEXED,\n message_id UNINDEXED,\n tokenize = 'unicode61'\n );\n\n CREATE TABLE IF NOT EXISTS file_jobs (\n id TEXT PRIMARY KEY,\n source_path TEXT NOT NULL,\n stored_path TEXT,\n file_name TEXT NOT NULL,\n status TEXT NOT NULL,\n parser TEXT,\n message_id TEXT,\n bytes INTEGER,\n characters INTEGER,\n warnings_json TEXT NOT NULL DEFAULT '[]',\n error TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n `);\n}\n","import fs from \"node:fs/promises\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport { getChatterCatcherHome } from \"../config/paths.js\";\nimport { getDatabasePath, openDatabase } from \"../db/database.js\";\nimport { FileJobRepository } from \"../files/jobs.js\";\nimport { getGatewayStatus } from \"../gateway/index.js\";\nimport { createChatModel, createEmbeddingModel } from \"../llm/openai-compatible.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { hasEmbeddingConfig } from \"../rag/factory.js\";\n\nexport type DoctorStatus = \"pass\" | \"warn\" | \"fail\";\n\nexport interface DoctorCheck {\n name: string;\n status: DoctorStatus;\n message: string;\n}\n\nexport interface DoctorOptions {\n online?: boolean;\n}\n\nfunction pass(name: string, message: string): DoctorCheck {\n return { name, status: \"pass\", message };\n}\n\nfunction warn(name: string, message: string): DoctorCheck {\n return { name, status: \"warn\", message };\n}\n\nfunction fail(name: string, message: string): DoctorCheck {\n return { name, status: \"fail\", message };\n}\n\nexport async function runDoctor(\n config: AppConfig,\n secrets: AppSecrets,\n options: DoctorOptions = {},\n): Promise<DoctorCheck[]> {\n const checks: DoctorCheck[] = [];\n\n checks.push(await checkHomeDirectory());\n checks.push(checkFeishu(config, secrets));\n checks.push(checkLlmConfig(config, secrets));\n checks.push(checkEmbeddingConfig(config, secrets));\n checks.push(await checkSqlite(config));\n checks.push(await checkFilePipeline(config));\n checks.push(await checkLanceDb(config));\n checks.push(checkRagPolicy());\n\n if (options.online) {\n checks.push(await checkChatModel(config, secrets));\n checks.push(await checkEmbeddingModel(config, secrets));\n }\n\n return checks;\n}\n\nasync function checkHomeDirectory(): Promise<DoctorCheck> {\n const home = getChatterCatcherHome();\n try {\n await fs.mkdir(home, { recursive: true });\n await fs.access(home);\n return pass(\"配置目录\", home);\n } catch (error) {\n return fail(\"配置目录\", error instanceof Error ? error.message : String(error));\n }\n}\n\nfunction checkFeishu(config: AppConfig, secrets: AppSecrets): DoctorCheck {\n const status = getGatewayStatus(config, secrets);\n if (status.configured) {\n return pass(\"飞书 Gateway\", status.message);\n }\n\n return warn(\"飞书 Gateway\", status.message);\n}\n\nfunction checkLlmConfig(config: AppConfig, secrets: AppSecrets): DoctorCheck {\n if (!config.llm.baseUrl || !config.llm.model || !secrets.llm.apiKey) {\n return warn(\"LLM 配置\", \"未配置完整;@ 提问时无法生成模型回答。\");\n }\n\n return pass(\"LLM 配置\", `${config.llm.model} @ ${config.llm.baseUrl}`);\n}\n\nfunction checkEmbeddingConfig(config: AppConfig, secrets: AppSecrets): DoctorCheck {\n if (!hasEmbeddingConfig(config, secrets)) {\n return warn(\"Embedding 配置\", \"未配置完整;RAG 会使用 SQLite FTS,无法使用 LanceDB 语义检索。\");\n }\n\n return pass(\"Embedding 配置\", `${config.embedding.model} @ ${config.embedding.baseUrl || config.llm.baseUrl}`);\n}\n\nasync function checkSqlite(config: AppConfig): Promise<DoctorCheck> {\n let database: ReturnType<typeof openDatabase> | null = null;\n try {\n database = openDatabase(config);\n const messages = new MessageRepository(database);\n return pass(\"SQLite\", `${getDatabasePath(config)};messages=${messages.getMessageCount()}`);\n } catch (error) {\n return fail(\"SQLite\", error instanceof Error ? error.message : String(error));\n } finally {\n database?.close();\n }\n}\n\nasync function checkFilePipeline(config: AppConfig): Promise<DoctorCheck> {\n let database: ReturnType<typeof openDatabase> | null = null;\n try {\n database = openDatabase(config);\n const messages = new MessageRepository(database);\n const jobs = new FileJobRepository(database);\n const fileCount = messages.listFiles(1_000_000).length;\n const failedJobs = jobs.list(1_000_000, { status: \"failed\" });\n\n if (failedJobs.length > 0) {\n return warn(\"文件解析\", `files=${fileCount};failed_jobs=${failedJobs.length};可运行 chattercatcher files jobs --status failed 查看。`);\n }\n\n return pass(\"文件解析\", `files=${fileCount};failed_jobs=0`);\n } catch (error) {\n return fail(\"文件解析\", error instanceof Error ? error.message : String(error));\n } finally {\n database?.close();\n }\n}\n\nasync function checkLanceDb(config: AppConfig): Promise<DoctorCheck> {\n let store: { count(): Promise<number>; close(): void } | null = null;\n try {\n const { getLanceDbPath, LanceDbVectorStore } = await import(\"../rag/lancedb-store.js\");\n store = await LanceDbVectorStore.connectFromConfig(config);\n const count = await store.count();\n return pass(\"LanceDB\", `${getLanceDbPath(config)};vectors=${count}`);\n } catch (error) {\n return fail(\"LanceDB\", error instanceof Error ? error.message : String(error));\n } finally {\n store?.close();\n }\n}\n\nfunction checkRagPolicy(): DoctorCheck {\n return pass(\"RAG 策略\", \"强制先检索证据再回答;禁止全量上下文堆叠。\");\n}\n\nasync function checkChatModel(config: AppConfig, secrets: AppSecrets): Promise<DoctorCheck> {\n if (!config.llm.baseUrl || !config.llm.model || !secrets.llm.apiKey) {\n return warn(\"LLM 连通性\", \"跳过:LLM 配置不完整。\");\n }\n\n try {\n const answer = await createChatModel(config, secrets).complete([{ role: \"user\", content: \"Reply with OK only.\" }]);\n return pass(\"LLM 连通性\", answer.slice(0, 80));\n } catch (error) {\n return fail(\"LLM 连通性\", error instanceof Error ? error.message : String(error));\n }\n}\n\nasync function checkEmbeddingModel(config: AppConfig, secrets: AppSecrets): Promise<DoctorCheck> {\n if (!hasEmbeddingConfig(config, secrets)) {\n return warn(\"Embedding 连通性\", \"跳过:Embedding 配置不完整。\");\n }\n\n try {\n const vector = await createEmbeddingModel(config, secrets).embed(\"chattercatcher doctor\");\n if (vector.length === 0) {\n return fail(\"Embedding 连通性\", \"返回向量为空。\");\n }\n\n return pass(\"Embedding 连通性\", `dimension=${vector.length}`);\n } catch (error) {\n return fail(\"Embedding 连通性\", error instanceof Error ? error.message : String(error));\n }\n}\n\nexport function formatDoctorChecks(checks: DoctorCheck[]): string {\n const icon: Record<DoctorStatus, string> = {\n pass: \"PASS\",\n warn: \"WARN\",\n fail: \"FAIL\",\n };\n\n return checks.map((check) => `[${icon[check.status]}] ${check.name}: ${check.message}`).join(\"\\n\");\n}\n","import crypto from \"node:crypto\";\nimport path from \"node:path\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport type FileJobStatus = \"processing\" | \"indexed\" | \"failed\";\n\nexport interface FileJobRecord {\n id: string;\n sourcePath: string;\n storedPath?: string;\n fileName: string;\n status: FileJobStatus;\n parser?: string;\n messageId?: string;\n bytes?: number;\n characters?: number;\n warnings: string[];\n error?: string;\n createdAt: string;\n updatedAt: string;\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction stableJobId(sourcePath: string): string {\n return crypto.createHash(\"sha256\").update(path.resolve(sourcePath)).digest(\"hex\").slice(0, 32);\n}\n\nfunction parseWarnings(value: string): string[] {\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === \"string\") : [];\n } catch {\n return [];\n }\n}\n\nexport class FileJobRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n start(input: { sourcePath: string; fileName?: string }): string {\n const id = stableJobId(input.sourcePath);\n const now = nowIso();\n this.database\n .prepare(\n `\n INSERT INTO file_jobs (\n id, source_path, file_name, status, warnings_json, created_at, updated_at\n )\n VALUES (@id, @sourcePath, @fileName, 'processing', '[]', @createdAt, @updatedAt)\n ON CONFLICT(id) DO UPDATE SET\n source_path = excluded.source_path,\n file_name = excluded.file_name,\n status = 'processing',\n parser = NULL,\n message_id = NULL,\n bytes = NULL,\n characters = NULL,\n warnings_json = '[]',\n error = NULL,\n updated_at = excluded.updated_at\n `,\n )\n .run({\n id,\n sourcePath: path.resolve(input.sourcePath),\n fileName: input.fileName ?? path.basename(input.sourcePath),\n createdAt: now,\n updatedAt: now,\n });\n return id;\n }\n\n complete(input: {\n id: string;\n storedPath: string;\n parser: string;\n messageId: string;\n bytes: number;\n characters: number;\n warnings: string[];\n }): void {\n this.database\n .prepare(\n `\n UPDATE file_jobs\n SET\n stored_path = @storedPath,\n status = 'indexed',\n parser = @parser,\n message_id = @messageId,\n bytes = @bytes,\n characters = @characters,\n warnings_json = @warningsJson,\n error = NULL,\n updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({\n id: input.id,\n storedPath: input.storedPath,\n parser: input.parser,\n messageId: input.messageId,\n bytes: input.bytes,\n characters: input.characters,\n warningsJson: JSON.stringify(input.warnings),\n updatedAt: nowIso(),\n });\n }\n\n fail(input: { id: string; error: string }): void {\n this.database\n .prepare(\n `\n UPDATE file_jobs\n SET status = 'failed', error = @error, updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({\n id: input.id,\n error: input.error,\n updatedAt: nowIso(),\n });\n }\n\n get(id: string): FileJobRecord | null {\n return this.listByWhere(\"WHERE id = ?\", [id], 1)[0] ?? null;\n }\n\n list(limit = 50, options: { status?: FileJobStatus } = {}): FileJobRecord[] {\n return options.status ? this.listByWhere(\"WHERE status = ?\", [options.status], limit) : this.listByWhere(\"\", [], limit);\n }\n\n private listByWhere(whereSql: string, params: unknown[], limit: number): FileJobRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n source_path AS sourcePath,\n stored_path AS storedPath,\n file_name AS fileName,\n status,\n parser,\n message_id AS messageId,\n bytes,\n characters,\n warnings_json AS warningsJson,\n error,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM file_jobs\n ${whereSql}\n ORDER BY updated_at DESC\n LIMIT ?\n `,\n )\n .all(...params, limit) as Array<{\n id: string;\n sourcePath: string;\n storedPath: string | null;\n fileName: string;\n status: FileJobStatus;\n parser: string | null;\n messageId: string | null;\n bytes: number | null;\n characters: number | null;\n warningsJson: string;\n error: string | null;\n createdAt: string;\n updatedAt: string;\n }>;\n\n return rows.map((row) => ({\n id: row.id,\n sourcePath: row.sourcePath,\n storedPath: row.storedPath ?? undefined,\n fileName: row.fileName,\n status: row.status,\n parser: row.parser ?? undefined,\n messageId: row.messageId ?? undefined,\n bytes: row.bytes ?? undefined,\n characters: row.characters ?? undefined,\n warnings: parseWarnings(row.warningsJson),\n error: row.error ?? undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n }));\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getChatterCatcherHome } from \"../config/paths.js\";\nimport { getLogsDirectory } from \"../logs/reader.js\";\n\nexport interface GatewayPidRecord {\n pid: number;\n startedAt: string;\n command: string;\n logFile?: string;\n mode?: \"gateway\" | \"web\";\n}\n\nexport interface GatewayRuntimeState {\n pidFile: string;\n record: GatewayPidRecord | null;\n running: boolean;\n stale: boolean;\n}\n\nexport interface StopGatewayResult {\n stopped: boolean;\n message: string;\n}\n\nexport function getGatewayPidPath(): string {\n return path.join(getChatterCatcherHome(), \"gateway.pid\");\n}\n\nexport function getGatewayLogPath(): string {\n return path.join(getLogsDirectory(), \"gateway.log\");\n}\n\nexport function isProcessRunning(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) {\n return false;\n }\n\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function readGatewayPidRecord(pidFile = getGatewayPidPath()): GatewayPidRecord | null {\n try {\n const raw = fs.readFileSync(pidFile, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<GatewayPidRecord>;\n if (!Number.isInteger(parsed.pid) || typeof parsed.startedAt !== \"string\" || typeof parsed.command !== \"string\") {\n return null;\n }\n\n const pid = parsed.pid;\n if (pid === undefined) {\n return null;\n }\n\n return {\n pid,\n startedAt: parsed.startedAt,\n command: parsed.command,\n ...(typeof parsed.logFile === \"string\" ? { logFile: parsed.logFile } : {}),\n ...(parsed.mode === \"gateway\" || parsed.mode === \"web\" ? { mode: parsed.mode } : {}),\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n\n return null;\n }\n}\n\nexport function writeGatewayPidRecord(pidFile = getGatewayPidPath(), record: GatewayPidRecord = {\n pid: process.pid,\n startedAt: new Date().toISOString(),\n command: process.argv.join(\" \"),\n}): void {\n fs.mkdirSync(path.dirname(pidFile), { recursive: true });\n fs.writeFileSync(pidFile, `${JSON.stringify(record, null, 2)}\\n`, \"utf8\");\n}\n\nexport function removeGatewayPidRecord(pidFile = getGatewayPidPath()): void {\n try {\n fs.rmSync(pidFile, { force: true });\n } catch {\n // Best-effort cleanup only.\n }\n}\n\nexport function getGatewayRuntimeState(pidFile = getGatewayPidPath()): GatewayRuntimeState {\n const record = readGatewayPidRecord(pidFile);\n const running = record ? isProcessRunning(record.pid) : false;\n return {\n pidFile,\n record,\n running,\n stale: Boolean(record && !running),\n };\n}\n\nexport function stopGatewayProcess(pidFile = getGatewayPidPath()): StopGatewayResult {\n const state = getGatewayRuntimeState(pidFile);\n if (!state.record) {\n return {\n stopped: false,\n message: \"Gateway 没有运行记录。\",\n };\n }\n\n if (state.stale) {\n removeGatewayPidRecord(pidFile);\n return {\n stopped: false,\n message: `Gateway PID 文件已过期,已清理:${state.record.pid}`,\n };\n }\n\n if (state.record.pid === process.pid) {\n return {\n stopped: false,\n message: \"拒绝停止当前 CLI 进程;请在另一个终端运行 stop,或按 Ctrl+C。\",\n };\n }\n\n try {\n process.kill(state.record.pid, \"SIGTERM\");\n removeGatewayPidRecord(pidFile);\n return {\n stopped: true,\n message: `已向 Gateway 进程发送停止信号:pid=${state.record.pid}`,\n };\n } catch (error) {\n return {\n stopped: false,\n message: `停止 Gateway 失败:${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n","import fs from \"node:fs/promises\";\nimport { watch } from \"node:fs\";\nimport path from \"node:path\";\nimport { getChatterCatcherHome } from \"../config/paths.js\";\n\nexport interface LogFileInfo {\n name: string;\n path: string;\n updatedAt: Date;\n bytes: number;\n}\n\nexport interface LogTailResult {\n file: LogFileInfo;\n content: string;\n}\n\nexport function getLogsDirectory(): string {\n return path.join(getChatterCatcherHome(), \"logs\");\n}\n\nexport function resolveLogPath(fileName: string, logsDir = getLogsDirectory()): string {\n return path.isAbsolute(fileName) ? fileName : path.join(logsDir, fileName);\n}\n\nexport function normalizeLineCount(value: number | string | undefined, fallback = 200): number {\n const parsed = Number(value ?? fallback);\n return Number.isFinite(parsed) ? Math.min(Math.max(Math.trunc(parsed), 1), 10_000) : fallback;\n}\n\nexport async function listLogFiles(logsDir = getLogsDirectory()): Promise<LogFileInfo[]> {\n let entries: Array<{ isFile: () => boolean; name: string }>;\n try {\n entries = await fs.readdir(logsDir, { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return [];\n }\n\n throw error;\n }\n\n const files = await Promise.all(\n entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".log\"))\n .map(async (entry) => {\n const filePath = path.join(logsDir, entry.name);\n const stats = await fs.stat(filePath);\n return {\n name: entry.name,\n path: filePath,\n updatedAt: stats.mtime,\n bytes: stats.size,\n };\n }),\n );\n\n return files.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());\n}\n\nfunction tailLines(content: string, lines: number): string {\n const normalized = content.replace(/\\r\\n/g, \"\\n\");\n const parts = normalized.endsWith(\"\\n\") ? normalized.slice(0, -1).split(\"\\n\") : normalized.split(\"\\n\");\n return parts.slice(-lines).join(\"\\n\");\n}\n\nexport async function readLogTail(input: { filePath: string; lines?: number }): Promise<LogTailResult> {\n const stats = await fs.stat(input.filePath);\n const content = await fs.readFile(input.filePath, \"utf8\");\n return {\n file: {\n name: path.basename(input.filePath),\n path: input.filePath,\n updatedAt: stats.mtime,\n bytes: stats.size,\n },\n content: tailLines(content, normalizeLineCount(input.lines)),\n };\n}\n\nexport async function readLatestLogTail(input: {\n fileName?: string;\n lines?: number;\n logsDir?: string;\n} = {}): Promise<LogTailResult | null> {\n if (input.fileName) {\n return readLogTail({\n filePath: resolveLogPath(input.fileName, input.logsDir),\n lines: input.lines,\n });\n }\n\n const [latest] = await listLogFiles(input.logsDir);\n if (!latest) {\n return null;\n }\n\n return readLogTail({ filePath: latest.path, lines: input.lines });\n}\n\nexport async function followLogFile(input: {\n filePath: string;\n onChunk: (chunk: string) => void;\n onError?: (error: Error) => void;\n}): Promise<() => void> {\n let offset = (await fs.stat(input.filePath)).size;\n const directory = path.dirname(input.filePath);\n const fileName = path.basename(input.filePath);\n\n async function readAppended(): Promise<void> {\n const stats = await fs.stat(input.filePath);\n if (stats.size < offset) {\n offset = 0;\n }\n\n if (stats.size === offset) {\n return;\n }\n\n const handle = await fs.open(input.filePath, \"r\");\n try {\n const length = stats.size - offset;\n const buffer = Buffer.alloc(length);\n await handle.read(buffer, 0, length, offset);\n offset = stats.size;\n input.onChunk(buffer.toString(\"utf8\"));\n } finally {\n await handle.close();\n }\n }\n\n const watcher = watch(directory, (eventType, changedFileName) => {\n if (eventType !== \"change\" || changedFileName?.toString() !== fileName) {\n return;\n }\n\n void readAppended().catch((error: unknown) => {\n input.onError?.(error instanceof Error ? error : new Error(String(error)));\n });\n });\n\n return () => watcher.close();\n}\n","import type { AppConfig } from \"../config/schema.js\";\nimport type { AppSecrets } from \"../config/schema.js\";\nimport { getGatewayRuntimeState } from \"./runtime.js\";\n\nexport interface GatewayStatus {\n configured: boolean;\n connection: \"not_configured\" | \"ready_for_start\" | \"running\";\n message: string;\n pid?: number;\n pidFile?: string;\n logFile?: string;\n}\n\nexport function getGatewayStatus(config: AppConfig, secrets?: AppSecrets): GatewayStatus {\n const runtime = getGatewayRuntimeState();\n const configured = Boolean(config.feishu.appId && (!secrets || secrets.feishu.appSecret));\n\n if (runtime.running && runtime.record) {\n if (runtime.record.mode === \"web\" && !configured) {\n return {\n configured,\n connection: \"running\",\n message: `本地 Web UI 进程正在运行:pid=${runtime.record.pid},startedAt=${runtime.record.startedAt};飞书配置尚未完成。`,\n pid: runtime.record.pid,\n pidFile: runtime.pidFile,\n logFile: runtime.record.logFile,\n };\n }\n\n return {\n configured: true,\n connection: \"running\",\n message: `飞书 Gateway 正在运行:pid=${runtime.record.pid},startedAt=${runtime.record.startedAt}`,\n pid: runtime.record.pid,\n pidFile: runtime.pidFile,\n logFile: runtime.record.logFile,\n };\n }\n\n if (!config.feishu.appId) {\n return {\n configured: false,\n connection: \"not_configured\",\n message: \"尚未配置飞书 App ID。请运行 chattercatcher setup 或 chattercatcher settings。\",\n };\n }\n\n if (secrets && !secrets.feishu.appSecret) {\n return {\n configured: false,\n connection: \"not_configured\",\n message: \"尚未配置飞书 App Secret。请运行 chattercatcher setup 或 chattercatcher settings。\",\n };\n }\n\n if (runtime.stale && runtime.record) {\n return {\n configured: true,\n connection: \"ready_for_start\",\n message: `飞书长连接配置已就绪;发现过期 PID 文件:pid=${runtime.record.pid}。运行 chattercatcher gateway start 会覆盖运行记录。`,\n pid: runtime.record.pid,\n pidFile: runtime.pidFile,\n logFile: runtime.record.logFile,\n };\n }\n\n return {\n configured: true,\n connection: \"ready_for_start\",\n message: \"飞书长连接配置已就绪。运行 chattercatcher gateway start 后会接收 im.message.receive_v1 事件。\",\n pidFile: runtime.pidFile,\n };\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { EmbeddingModel } from \"../rag/embedding.js\";\nimport type { ChatMessage, ChatModel } from \"../rag/types.js\";\n\nexport interface OpenAICompatibleChatOptions {\n baseUrl: string;\n apiKey: string;\n model: string;\n temperature?: number;\n}\n\ninterface ChatCompletionResponse {\n choices?: Array<{\n message?: {\n content?: string;\n };\n }>;\n}\n\ninterface EmbeddingResponse {\n data?: Array<{\n embedding?: number[];\n }>;\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\");\n}\n\nexport class OpenAICompatibleChatModel implements ChatModel {\n constructor(private readonly options: OpenAICompatibleChatOptions) {}\n\n async complete(messages: ChatMessage[]): Promise<string> {\n if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {\n throw new Error(\"LLM 配置不完整。请运行 chattercatcher setup 或 chattercatcher settings。\");\n }\n\n const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/chat/completions`, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${this.options.apiKey}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n model: this.options.model,\n messages,\n temperature: this.options.temperature ?? 0.2,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`LLM 请求失败:${response.status} ${body}`);\n }\n\n const data = (await response.json()) as ChatCompletionResponse;\n const content = data.choices?.[0]?.message?.content?.trim();\n if (!content) {\n throw new Error(\"LLM 返回为空。\");\n }\n\n return content;\n }\n}\n\nexport interface OpenAICompatibleEmbeddingOptions {\n baseUrl: string;\n apiKey: string;\n model: string;\n}\n\nexport class OpenAICompatibleEmbeddingModel implements EmbeddingModel {\n constructor(private readonly options: OpenAICompatibleEmbeddingOptions) {}\n\n async embed(text: string): Promise<number[]> {\n const [vector] = await this.embedBatch([text]);\n return vector ?? [];\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {\n throw new Error(\"Embedding 配置不完整。请运行 chattercatcher setup 或 chattercatcher settings。\");\n }\n\n const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/embeddings`, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${this.options.apiKey}`,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({\n model: this.options.model,\n input: texts,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Embedding 请求失败:${response.status} ${body}`);\n }\n\n const data = (await response.json()) as EmbeddingResponse;\n return data.data?.map((item) => item.embedding ?? []) ?? [];\n }\n}\n\nexport function createChatModel(config: AppConfig, secrets: AppSecrets): OpenAICompatibleChatModel {\n return new OpenAICompatibleChatModel({\n baseUrl: config.llm.baseUrl,\n apiKey: secrets.llm.apiKey,\n model: config.llm.model,\n });\n}\n\nexport function createEmbeddingModel(config: AppConfig, secrets: AppSecrets): OpenAICompatibleEmbeddingModel {\n return new OpenAICompatibleEmbeddingModel({\n baseUrl: config.embedding.baseUrl || config.llm.baseUrl,\n apiKey: secrets.embedding.apiKey || secrets.llm.apiKey,\n model: config.embedding.model,\n });\n}\n","import crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { chunkText } from \"./chunker.js\";\nimport type { ChatRecord, FileRecord, IngestMessageInput, MessageSearchResult } from \"./types.js\";\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction stableId(parts: string[]): string {\n return crypto.createHash(\"sha256\").update(parts.join(\"\\u001f\")).digest(\"hex\").slice(0, 32);\n}\n\nfunction escapeFtsQuery(query: string): string {\n const terms = query\n .trim()\n .split(/\\s+/)\n .map((term) => term.replace(/\"/g, \"\\\"\\\"\"))\n .filter(Boolean);\n\n if (terms.length === 0) {\n return \"\\\"\\\"\";\n }\n\n return terms.map((term) => `\"${term}\"`).join(\" OR \");\n}\n\nfunction escapeLikeTerm(term: string): string {\n return term.replace(/[\\\\%_]/g, (match) => `\\\\${match}`);\n}\n\nfunction buildSearchTerms(query: string): string[] {\n const trimmed = query.trim();\n if (!trimmed) {\n return [];\n }\n\n const terms = trimmed.split(/\\s+/).filter(Boolean);\n if (terms.length > 1) {\n return terms;\n }\n\n if (/[\\u3400-\\u9fff]/.test(trimmed) && trimmed.length > 2) {\n const cjkTerms = new Set<string>([trimmed]);\n for (let index = 0; index < trimmed.length - 1; index += 1) {\n cjkTerms.add(trimmed.slice(index, index + 2));\n }\n\n return [...cjkTerms];\n }\n\n return [trimmed];\n}\n\nfunction parseRawPayload(value: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nexport class MessageRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n ingest(input: IngestMessageInput): string {\n const createdAt = nowIso();\n const chatId = stableId([input.platform, input.platformChatId]);\n const messageId = stableId([input.platform, input.platformMessageId]);\n const rawPayloadJson = JSON.stringify(input.rawPayload ?? {});\n const chunks = chunkText(input.text);\n\n const transaction = this.database.transaction(() => {\n this.database\n .prepare(\n `\n INSERT INTO chats (id, platform, platform_chat_id, name, created_at, updated_at)\n VALUES (@id, @platform, @platformChatId, @name, @createdAt, @updatedAt)\n ON CONFLICT(platform, platform_chat_id)\n DO UPDATE SET name = excluded.name, updated_at = excluded.updated_at\n `,\n )\n .run({\n id: chatId,\n platform: input.platform,\n platformChatId: input.platformChatId,\n name: input.chatName,\n createdAt,\n updatedAt: createdAt,\n });\n\n this.database\n .prepare(\n `\n INSERT INTO messages (\n id, platform, platform_message_id, chat_id, sender_id, sender_name,\n message_type, text, raw_payload_json, sent_at, received_at, created_at\n )\n VALUES (\n @id, @platform, @platformMessageId, @chatId, @senderId, @senderName,\n @messageType, @text, @rawPayloadJson, @sentAt, @receivedAt, @createdAt\n )\n ON CONFLICT(platform, platform_message_id)\n DO UPDATE SET\n text = excluded.text,\n raw_payload_json = excluded.raw_payload_json,\n received_at = excluded.received_at\n `,\n )\n .run({\n id: messageId,\n platform: input.platform,\n platformMessageId: input.platformMessageId,\n chatId,\n senderId: input.senderId,\n senderName: input.senderName,\n messageType: input.messageType,\n text: input.text,\n rawPayloadJson,\n sentAt: input.sentAt,\n receivedAt: createdAt,\n createdAt,\n });\n\n this.database.prepare(\"DELETE FROM message_chunks_fts WHERE message_id = ?\").run(messageId);\n this.database.prepare(\"DELETE FROM message_chunks WHERE message_id = ?\").run(messageId);\n\n const insertChunk = this.database.prepare(`\n INSERT INTO message_chunks (id, message_id, chunk_index, text, metadata_json, created_at)\n VALUES (@id, @messageId, @chunkIndex, @text, @metadataJson, @createdAt)\n `);\n const insertFts = this.database.prepare(`\n INSERT INTO message_chunks_fts (text, chunk_id, message_id)\n VALUES (@text, @chunkId, @messageId)\n `);\n\n for (const chunk of chunks) {\n const chunkId = stableId([messageId, String(chunk.index)]);\n insertChunk.run({\n id: chunkId,\n messageId,\n chunkIndex: chunk.index,\n text: chunk.text,\n metadataJson: JSON.stringify({ sourceType: \"message\" }),\n createdAt,\n });\n insertFts.run({ text: chunk.text, chunkId, messageId });\n }\n });\n\n transaction();\n return messageId;\n }\n\n listRecentMessages(limit = 20): MessageSearchResult[] {\n return this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n m.id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n 1.0 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks mc\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n ORDER BY m.sent_at DESC\n LIMIT ?\n `,\n )\n .all(limit) as MessageSearchResult[];\n }\n\n listAllMessageChunks(limit = 10000): MessageSearchResult[] {\n return this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n m.id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n 1.0 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks mc\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n ORDER BY m.sent_at DESC, mc.chunk_index ASC\n LIMIT ?\n `,\n )\n .all(limit) as MessageSearchResult[];\n }\n\n listMessageChunksByMessageIds(messageIds: string[], limit = 10000): MessageSearchResult[] {\n if (messageIds.length === 0) {\n return [];\n }\n\n return this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n m.id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n 1.0 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks mc\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n WHERE m.id IN (${messageIds.map(() => \"?\").join(\", \")})\n ORDER BY m.sent_at DESC, mc.chunk_index ASC\n LIMIT ?\n `,\n )\n .all(...messageIds, limit) as MessageSearchResult[];\n }\n\n searchMessages(query: string, limit = 8, options: { excludeMessageIds?: string[] } = {}): MessageSearchResult[] {\n const ftsQuery = escapeFtsQuery(query);\n const excludedIds = options.excludeMessageIds ?? [];\n const excludedWhere = excludedIds.length > 0 ? `AND fts.message_id NOT IN (${excludedIds.map(() => \"?\").join(\", \")})` : \"\";\n const ftsResults = this.database\n .prepare(\n `\n SELECT\n fts.chunk_id AS chunkId,\n fts.message_id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n bm25(message_chunks_fts) * -1 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks_fts fts\n JOIN message_chunks mc ON mc.id = fts.chunk_id\n JOIN messages m ON m.id = fts.message_id\n JOIN chats c ON c.id = m.chat_id\n WHERE message_chunks_fts MATCH ?\n ${excludedWhere}\n ORDER BY bm25(message_chunks_fts)\n LIMIT ?\n `,\n )\n .all(ftsQuery, ...excludedIds, limit) as MessageSearchResult[];\n\n if (ftsResults.length > 0) {\n return ftsResults;\n }\n\n const terms = buildSearchTerms(query);\n if (terms.length === 0) {\n return [];\n }\n\n const where = terms.map(() => \"mc.text LIKE ? ESCAPE '\\\\'\").join(\" OR \");\n const params = terms.map((term) => `%${escapeLikeTerm(term)}%`);\n const likeExcludedWhere =\n excludedIds.length > 0 ? `AND m.id NOT IN (${excludedIds.map(() => \"?\").join(\", \")})` : \"\";\n\n return this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n m.id AS messageId,\n m.platform AS platform,\n mc.text AS text,\n 0.1 AS score,\n m.message_type AS messageType,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt\n FROM message_chunks mc\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n WHERE (${where})\n ${likeExcludedWhere}\n ORDER BY m.sent_at DESC\n LIMIT ?\n `,\n )\n .all(...params, ...excludedIds, limit) as MessageSearchResult[];\n }\n\n getChatCount(): number {\n return (this.database.prepare(\"SELECT COUNT(*) AS count FROM chats\").get() as { count: number }).count;\n }\n\n getMessageCount(): number {\n return (this.database.prepare(\"SELECT COUNT(*) AS count FROM messages\").get() as { count: number }).count;\n }\n\n hasPlatformMessage(platform: string, platformMessageId: string): boolean {\n const row = this.database\n .prepare(\"SELECT 1 AS existsFlag FROM messages WHERE platform = ? AND platform_message_id = ? LIMIT 1\")\n .get(platform, platformMessageId) as { existsFlag: number } | undefined;\n return Boolean(row);\n }\n\n listChats(): ChatRecord[] {\n return this.database\n .prepare(\n `\n SELECT\n id,\n platform,\n platform_chat_id AS platformChatId,\n name,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM chats\n ORDER BY updated_at DESC\n `,\n )\n .all() as ChatRecord[];\n }\n\n listFiles(limit = 50): FileRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id AS messageId,\n sender_name AS fileName,\n raw_payload_json AS rawPayloadJson,\n length(text) AS characters,\n created_at AS importedAt\n FROM messages\n WHERE message_type = 'file'\n ORDER BY created_at DESC\n LIMIT ?\n `,\n )\n .all(limit) as Array<{\n messageId: string;\n fileName: string;\n rawPayloadJson: string;\n characters: number;\n importedAt: string;\n }>;\n\n return rows.map((row) => {\n const payload = parseRawPayload(row.rawPayloadJson);\n return {\n messageId: row.messageId,\n fileName: row.fileName,\n sourcePath: typeof payload.sourcePath === \"string\" ? payload.sourcePath : undefined,\n storedPath: typeof payload.storedPath === \"string\" ? payload.storedPath : undefined,\n bytes: typeof payload.bytes === \"number\" ? payload.bytes : undefined,\n characters: row.characters,\n parser: typeof payload.parser === \"string\" ? payload.parser : undefined,\n parserWarnings: Array.isArray(payload.parserWarnings)\n ? payload.parserWarnings.filter((item): item is string => typeof item === \"string\")\n : undefined,\n importedAt: row.importedAt,\n };\n });\n }\n}\n","export interface TextChunk {\n index: number;\n text: string;\n}\n\nexport function chunkText(text: string, maxChars = 900, overlapChars = 120): TextChunk[] {\n const normalized = text.trim().replace(/\\s+/g, \" \");\n if (!normalized) {\n return [];\n }\n\n if (normalized.length <= maxChars) {\n return [{ index: 0, text: normalized }];\n }\n\n const chunks: TextChunk[] = [];\n let cursor = 0;\n\n while (cursor < normalized.length) {\n const end = Math.min(cursor + maxChars, normalized.length);\n chunks.push({ index: chunks.length, text: normalized.slice(cursor, end) });\n\n if (end === normalized.length) {\n break;\n }\n\n cursor = Math.max(end - overlapChars, cursor + 1);\n }\n\n return chunks;\n}\n\n","import type { Retriever } from \"./retriever.js\";\nimport type { EvidenceBlock } from \"./types.js\";\n\nexport interface HybridRetrieverOptions {\n limit?: number;\n}\n\nfunction normalizeScore(score: number): number {\n if (!Number.isFinite(score)) {\n return 0;\n }\n\n return Math.max(0, Math.min(1, score));\n}\n\nexport class HybridRetriever implements Retriever {\n constructor(\n private readonly retrievers: Retriever[],\n private readonly options: HybridRetrieverOptions = {},\n ) {}\n\n async retrieve(question: string): Promise<EvidenceBlock[]> {\n const results = await Promise.all(this.retrievers.map((retriever) => retriever.retrieve(question)));\n const merged = new Map<string, EvidenceBlock>();\n\n for (const [retrieverIndex, evidenceList] of results.entries()) {\n for (const evidence of evidenceList) {\n const existing = merged.get(evidence.id);\n const weightedScore = normalizeScore(evidence.score) + (this.retrievers.length - retrieverIndex) * 0.01;\n\n if (!existing || weightedScore > existing.score) {\n merged.set(evidence.id, {\n ...evidence,\n score: weightedScore,\n });\n }\n }\n }\n\n return [...merged.values()]\n .sort((left, right) => right.score - left.score)\n .slice(0, this.options.limit ?? 8);\n }\n}\n\n","import { MessageRepository } from \"../messages/repository.js\";\nimport type { MessageSearchResult } from \"../messages/types.js\";\nimport type { EvidenceBlock } from \"./types.js\";\nimport type { Retriever } from \"./retriever.js\";\n\nfunction toEvidenceSource(result: MessageSearchResult): EvidenceBlock[\"source\"] {\n if (result.messageType === \"file\") {\n return {\n type: \"file\",\n label: result.senderName,\n timestamp: result.sentAt,\n };\n }\n\n return {\n type: \"message\",\n label: result.chatName,\n sender: result.senderName,\n timestamp: result.sentAt,\n };\n}\n\nexport class MessageFtsRetriever implements Retriever {\n constructor(\n private readonly messages: MessageRepository,\n private readonly options: { excludeMessageIds?: string[] } = {},\n ) {}\n\n async retrieve(question: string): Promise<EvidenceBlock[]> {\n const results = this.messages.searchMessages(question, 8, {\n excludeMessageIds: this.options.excludeMessageIds,\n });\n\n return results.map((result) => ({\n id: result.chunkId,\n text: result.text,\n score: result.score,\n source: toEvidenceSource(result),\n }));\n }\n}\n","import type { EmbeddingModel } from \"./embedding.js\";\nimport type { Retriever } from \"./retriever.js\";\nimport type { EvidenceBlock } from \"./types.js\";\nimport type { VectorStore } from \"./vector-store.js\";\n\nexport class VectorRetriever implements Retriever {\n constructor(\n private readonly embedding: EmbeddingModel,\n private readonly store: VectorStore,\n private readonly limit = 8,\n ) {}\n\n async retrieve(question: string): Promise<EvidenceBlock[]> {\n const vector = await this.embedding.embed(question);\n return this.store.search(vector, this.limit);\n }\n}\n\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport { createEmbeddingModel } from \"../llm/openai-compatible.js\";\nimport { HybridRetriever } from \"./hybrid-retriever.js\";\nimport { MessageFtsRetriever } from \"./message-retriever.js\";\nimport type { Retriever } from \"./retriever.js\";\nimport { VectorRetriever } from \"./vector-retriever.js\";\n\nexport function hasEmbeddingConfig(config: AppConfig, secrets: AppSecrets): boolean {\n return Boolean((config.embedding.baseUrl || config.llm.baseUrl) && config.embedding.model && (secrets.embedding.apiKey || secrets.llm.apiKey));\n}\n\nexport async function createHybridRetriever(input: {\n config: AppConfig;\n secrets: AppSecrets;\n messages: MessageRepository;\n excludeMessageIds?: string[];\n}): Promise<{ retriever: Retriever; close: () => void }> {\n const retrievers: Retriever[] = [new MessageFtsRetriever(input.messages, { excludeMessageIds: input.excludeMessageIds })];\n const closers: Array<() => void> = [];\n\n if (hasEmbeddingConfig(input.config, input.secrets)) {\n const { LanceDbVectorStore } = await import(\"./lancedb-store.js\");\n const vectorStore = await LanceDbVectorStore.connectFromConfig(input.config);\n retrievers.push(new VectorRetriever(createEmbeddingModel(input.config, input.secrets), vectorStore));\n closers.push(() => vectorStore.close());\n }\n\n return {\n retriever: new HybridRetriever(retrievers),\n close: () => {\n for (const closer of closers) {\n closer();\n }\n },\n };\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport interface DataExportResult {\n outputPath: string;\n chats: number;\n messages: number;\n chunks: number;\n fileJobs: number;\n}\n\nfunction parseJsonObject(value: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nfunction parseJsonArray(value: string): unknown[] {\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n}\n\nfunction defaultExportPath(config: AppConfig, exportedAt: string): string {\n const fileName = `chattercatcher-export-${exportedAt.replace(/[:.]/g, \"-\")}.json`;\n return path.join(resolveHomePath(config.storage.dataDir), \"exports\", fileName);\n}\n\nexport async function exportLocalData(input: {\n config: AppConfig;\n database: SqliteDatabase;\n outputPath?: string;\n exportedAt?: string;\n}): Promise<DataExportResult> {\n const exportedAt = input.exportedAt ?? new Date().toISOString();\n const outputPath = path.resolve(input.outputPath ?? defaultExportPath(input.config, exportedAt));\n\n const chats = input.database\n .prepare(\n `\n SELECT\n id,\n platform,\n platform_chat_id AS platformChatId,\n name,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM chats\n ORDER BY updated_at ASC\n `,\n )\n .all();\n\n const messages = (\n input.database\n .prepare(\n `\n SELECT\n id,\n platform,\n platform_message_id AS platformMessageId,\n chat_id AS chatId,\n sender_id AS senderId,\n sender_name AS senderName,\n message_type AS messageType,\n text,\n raw_payload_json AS rawPayloadJson,\n sent_at AS sentAt,\n received_at AS receivedAt,\n created_at AS createdAt\n FROM messages\n ORDER BY sent_at ASC, created_at ASC\n `,\n )\n .all() as Array<Record<string, unknown> & { rawPayloadJson: string }>\n ).map(({ rawPayloadJson, ...message }) => ({\n ...message,\n rawPayload: parseJsonObject(rawPayloadJson),\n }));\n\n const chunks = (\n input.database\n .prepare(\n `\n SELECT\n id,\n message_id AS messageId,\n chunk_index AS chunkIndex,\n text,\n metadata_json AS metadataJson,\n created_at AS createdAt\n FROM message_chunks\n ORDER BY message_id ASC, chunk_index ASC\n `,\n )\n .all() as Array<Record<string, unknown> & { metadataJson: string }>\n ).map(({ metadataJson, ...chunk }) => ({\n ...chunk,\n metadata: parseJsonObject(metadataJson),\n }));\n\n const fileJobs = (\n input.database\n .prepare(\n `\n SELECT\n id,\n source_path AS sourcePath,\n stored_path AS storedPath,\n file_name AS fileName,\n status,\n parser,\n message_id AS messageId,\n bytes,\n characters,\n warnings_json AS warningsJson,\n error,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM file_jobs\n ORDER BY updated_at ASC\n `,\n )\n .all() as Array<Record<string, unknown> & { warningsJson: string }>\n ).map(({ warningsJson, ...job }) => ({\n ...job,\n warnings: parseJsonArray(warningsJson).filter((item): item is string => typeof item === \"string\"),\n }));\n\n const payload = {\n app: \"ChatterCatcher\",\n schemaVersion: 1,\n exportedAt,\n note: \"本文件只包含本地知识库数据,不包含 API Key、App Secret 或 token。\",\n data: {\n chats,\n messages,\n chunks,\n fileJobs,\n },\n };\n\n await fs.mkdir(path.dirname(outputPath), { recursive: true });\n await fs.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}\\n`, \"utf8\");\n\n return {\n outputPath,\n chats: chats.length,\n messages: messages.length,\n chunks: chunks.length,\n fileJobs: fileJobs.length,\n };\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport interface DataRestoreResult {\n inputPath: string;\n mode: \"merge\" | \"replace\";\n chats: number;\n messages: number;\n chunks: number;\n fileJobs: number;\n}\n\ninterface ExportPayload {\n app: string;\n schemaVersion: number;\n data: {\n chats: Array<Record<string, unknown>>;\n messages: Array<Record<string, unknown>>;\n chunks: Array<Record<string, unknown>>;\n fileJobs: Array<Record<string, unknown>>;\n };\n}\n\nfunction asObject(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};\n}\n\nfunction asArray(value: unknown): Array<Record<string, unknown>> {\n return Array.isArray(value) ? value.map(asObject) : [];\n}\n\nfunction asString(value: unknown, field: string): string {\n if (typeof value !== \"string\") {\n throw new Error(`恢复文件字段无效:${field}`);\n }\n\n return value;\n}\n\nfunction asOptionalString(value: unknown): string | null {\n return typeof value === \"string\" ? value : null;\n}\n\nfunction asOptionalNumber(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction asJson(value: unknown, fallback: unknown): string {\n return JSON.stringify(value === undefined ? fallback : value);\n}\n\nfunction parsePayload(raw: string): ExportPayload {\n const parsed = asObject(JSON.parse(raw) as unknown);\n const data = asObject(parsed.data);\n const payload = {\n app: asString(parsed.app, \"app\"),\n schemaVersion: typeof parsed.schemaVersion === \"number\" ? parsed.schemaVersion : NaN,\n data: {\n chats: asArray(data.chats),\n messages: asArray(data.messages),\n chunks: asArray(data.chunks),\n fileJobs: asArray(data.fileJobs),\n },\n };\n\n if (payload.app !== \"ChatterCatcher\" || payload.schemaVersion !== 1) {\n throw new Error(\"恢复文件不是 ChatterCatcher schemaVersion=1 导出。\");\n }\n\n return payload;\n}\n\nfunction clearDatabase(database: SqliteDatabase): void {\n database.prepare(\"DELETE FROM message_chunks_fts\").run();\n database.prepare(\"DELETE FROM message_chunks\").run();\n database.prepare(\"DELETE FROM file_jobs\").run();\n database.prepare(\"DELETE FROM messages\").run();\n database.prepare(\"DELETE FROM chats\").run();\n}\n\nexport async function restoreLocalData(input: {\n database: SqliteDatabase;\n inputPath: string;\n replace?: boolean;\n}): Promise<DataRestoreResult> {\n const inputPath = path.resolve(input.inputPath);\n const payload = parsePayload(await fs.readFile(inputPath, \"utf8\"));\n const mode = input.replace ? \"replace\" : \"merge\";\n\n const restore = input.database.transaction(() => {\n if (input.replace) {\n clearDatabase(input.database);\n }\n\n const upsertChat = input.database.prepare(`\n INSERT INTO chats (id, platform, platform_chat_id, name, created_at, updated_at)\n VALUES (@id, @platform, @platformChatId, @name, @createdAt, @updatedAt)\n ON CONFLICT(id) DO UPDATE SET\n platform = excluded.platform,\n platform_chat_id = excluded.platform_chat_id,\n name = excluded.name,\n updated_at = excluded.updated_at\n `);\n\n const upsertMessage = input.database.prepare(`\n INSERT INTO messages (\n id, platform, platform_message_id, chat_id, sender_id, sender_name,\n message_type, text, raw_payload_json, sent_at, received_at, created_at\n )\n VALUES (\n @id, @platform, @platformMessageId, @chatId, @senderId, @senderName,\n @messageType, @text, @rawPayloadJson, @sentAt, @receivedAt, @createdAt\n )\n ON CONFLICT(id) DO UPDATE SET\n platform = excluded.platform,\n platform_message_id = excluded.platform_message_id,\n chat_id = excluded.chat_id,\n sender_id = excluded.sender_id,\n sender_name = excluded.sender_name,\n message_type = excluded.message_type,\n text = excluded.text,\n raw_payload_json = excluded.raw_payload_json,\n sent_at = excluded.sent_at,\n received_at = excluded.received_at\n `);\n\n const upsertChunk = input.database.prepare(`\n INSERT INTO message_chunks (id, message_id, chunk_index, text, metadata_json, created_at)\n VALUES (@id, @messageId, @chunkIndex, @text, @metadataJson, @createdAt)\n ON CONFLICT(id) DO UPDATE SET\n message_id = excluded.message_id,\n chunk_index = excluded.chunk_index,\n text = excluded.text,\n metadata_json = excluded.metadata_json\n `);\n\n const insertFts = input.database.prepare(`\n INSERT INTO message_chunks_fts (text, chunk_id, message_id)\n VALUES (@text, @chunkId, @messageId)\n `);\n\n const upsertFileJob = input.database.prepare(`\n INSERT INTO file_jobs (\n id, source_path, stored_path, file_name, status, parser, message_id,\n bytes, characters, warnings_json, error, created_at, updated_at\n )\n VALUES (\n @id, @sourcePath, @storedPath, @fileName, @status, @parser, @messageId,\n @bytes, @characters, @warningsJson, @error, @createdAt, @updatedAt\n )\n ON CONFLICT(id) DO UPDATE SET\n source_path = excluded.source_path,\n stored_path = excluded.stored_path,\n file_name = excluded.file_name,\n status = excluded.status,\n parser = excluded.parser,\n message_id = excluded.message_id,\n bytes = excluded.bytes,\n characters = excluded.characters,\n warnings_json = excluded.warnings_json,\n error = excluded.error,\n updated_at = excluded.updated_at\n `);\n\n for (const chat of payload.data.chats) {\n upsertChat.run({\n id: asString(chat.id, \"chat.id\"),\n platform: asString(chat.platform, \"chat.platform\"),\n platformChatId: asString(chat.platformChatId, \"chat.platformChatId\"),\n name: asString(chat.name, \"chat.name\"),\n createdAt: asString(chat.createdAt, \"chat.createdAt\"),\n updatedAt: asString(chat.updatedAt, \"chat.updatedAt\"),\n });\n }\n\n for (const message of payload.data.messages) {\n upsertMessage.run({\n id: asString(message.id, \"message.id\"),\n platform: asString(message.platform, \"message.platform\"),\n platformMessageId: asString(message.platformMessageId, \"message.platformMessageId\"),\n chatId: asString(message.chatId, \"message.chatId\"),\n senderId: asString(message.senderId, \"message.senderId\"),\n senderName: asString(message.senderName, \"message.senderName\"),\n messageType: asString(message.messageType, \"message.messageType\"),\n text: asString(message.text, \"message.text\"),\n rawPayloadJson: asJson(message.rawPayload, {}),\n sentAt: asString(message.sentAt, \"message.sentAt\"),\n receivedAt: asString(message.receivedAt, \"message.receivedAt\"),\n createdAt: asString(message.createdAt, \"message.createdAt\"),\n });\n input.database.prepare(\"DELETE FROM message_chunks_fts WHERE message_id = ?\").run(asString(message.id, \"message.id\"));\n }\n\n for (const chunk of payload.data.chunks) {\n const messageId = asString(chunk.messageId, \"chunk.messageId\");\n const chunkId = asString(chunk.id, \"chunk.id\");\n const text = asString(chunk.text, \"chunk.text\");\n upsertChunk.run({\n id: chunkId,\n messageId,\n chunkIndex: asOptionalNumber(chunk.chunkIndex) ?? 0,\n text,\n metadataJson: asJson(chunk.metadata, {}),\n createdAt: asString(chunk.createdAt, \"chunk.createdAt\"),\n });\n insertFts.run({ text, chunkId, messageId });\n }\n\n for (const job of payload.data.fileJobs) {\n upsertFileJob.run({\n id: asString(job.id, \"fileJob.id\"),\n sourcePath: asString(job.sourcePath, \"fileJob.sourcePath\"),\n storedPath: asOptionalString(job.storedPath),\n fileName: asString(job.fileName, \"fileJob.fileName\"),\n status: asString(job.status, \"fileJob.status\"),\n parser: asOptionalString(job.parser),\n messageId: asOptionalString(job.messageId),\n bytes: asOptionalNumber(job.bytes),\n characters: asOptionalNumber(job.characters),\n warningsJson: asJson(Array.isArray(job.warnings) ? job.warnings : [], []),\n error: asOptionalString(job.error),\n createdAt: asString(job.createdAt, \"fileJob.createdAt\"),\n updatedAt: asString(job.updatedAt, \"fileJob.updatedAt\"),\n });\n }\n });\n\n restore();\n\n return {\n inputPath,\n mode,\n chats: payload.data.chats.length,\n messages: payload.data.messages.length,\n chunks: payload.data.chunks.length,\n fileJobs: payload.data.fileJobs.length,\n };\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { GatewayIngestAndDownloadResult, GatewayIngestor } from \"../gateway/ingest.js\";\nimport type { FeishuReceiveMessageEvent } from \"./normalize.js\";\nimport { getFeishuQuestionDecision, isFeishuMessageAddressedToBot } from \"./question.js\";\nimport type { FeishuQuestionHandler } from \"./question.js\";\nimport { FeishuResourceDownloader } from \"./resource-downloader.js\";\nimport { mapDomain } from \"./sender.js\";\n\nexport interface FeishuGatewayRuntime {\n start(): Promise<void>;\n stop(): void;\n}\n\ninterface WsClientLike {\n start(params: { eventDispatcher: lark.EventDispatcher }): Promise<void>;\n close(params?: { force?: boolean }): void;\n}\n\nexport interface FeishuGatewayOptions {\n config: AppConfig;\n secrets: AppSecrets;\n ingestor: GatewayIngestor;\n questionHandler?: FeishuQuestionHandler;\n resourceDownloader?: FeishuResourceDownloader;\n attachmentVectorIndexer?: (messageId: string) => Promise<{ chunks: number; vectors: number }>;\n wsClientFactory?: (params: {\n appId: string;\n appSecret: string;\n domain: lark.Domain;\n onReady: () => void;\n onError: (error: Error) => void;\n onReconnecting: () => void;\n onReconnected: () => void;\n }) => WsClientLike;\n}\n\nfunction assertFeishuConfig(config: AppConfig, secrets: AppSecrets): void {\n if (!config.feishu.appId || !secrets.feishu.appSecret) {\n throw new Error(\"飞书配置不完整。请先运行 chattercatcher setup 或 chattercatcher settings。\");\n }\n}\n\nexport function createFeishuEventDispatcher(options: {\n config: AppConfig;\n ingestor: GatewayIngestor;\n questionHandler?: FeishuQuestionHandler;\n resourceDownloader?: FeishuResourceDownloader;\n attachmentVectorIndexer?: (messageId: string) => Promise<{ chunks: number; vectors: number }>;\n}): lark.EventDispatcher {\n const answeredMessageIds = new Set<string>();\n\n return new lark.EventDispatcher({}).register({\n \"im.message.receive_v1\": async (data: FeishuReceiveMessageEvent[\"event\"]) => {\n const payload = { event: data };\n\n if (options.questionHandler && isFeishuMessageAddressedToBot(payload)) {\n const platformMessageId = data?.message?.message_id;\n if (platformMessageId && answeredMessageIds.has(platformMessageId)) {\n console.log(\"飞书提问重复投递:已跳过回答。\");\n return;\n }\n\n const decision = getFeishuQuestionDecision(payload, options.config);\n if (decision.shouldAnswer) {\n if (platformMessageId) {\n answeredMessageIds.add(platformMessageId);\n }\n await options.questionHandler.handle(payload);\n console.log(\"飞书提问已回答:跳过知识库入库。\");\n return;\n }\n }\n\n const result: GatewayIngestAndDownloadResult = options.resourceDownloader\n ? await options.ingestor.ingestFeishuEventAndDownloadAttachments({\n payload,\n downloader: options.resourceDownloader,\n config: options.config,\n vectorIndexMessage: options.attachmentVectorIndexer,\n })\n : options.ingestor.ingestFeishuEvent(payload);\n\n if (!result.accepted) {\n console.log(`飞书消息未入库:${result.reason}`);\n return;\n }\n\n console.log(`飞书消息已入库:${result.messageId}`);\n if (result.duplicate) {\n console.log(\"飞书消息重复投递:已跳过附件处理和回答。\");\n return;\n }\n\n if (result.attachment?.downloaded) {\n console.log(`飞书附件已下载:${result.attachment.downloaded.storedPath}`);\n if (result.attachment.indexedMessageId) {\n console.log(`飞书附件已进入 RAG:${result.attachment.indexedMessageId}`);\n if (result.attachment.vectorIndexed) {\n console.log(\n `飞书附件向量索引完成:chunks=${result.attachment.vectorIndexed.chunks}, vectors=${result.attachment.vectorIndexed.vectors}`,\n );\n }\n } else if (result.attachment.skippedReason) {\n console.log(`飞书附件暂未进入 RAG:${result.attachment.skippedReason}`);\n }\n }\n\n if (options.questionHandler) {\n const decision = await options.questionHandler.handle(payload, {\n excludeMessageIds: result.messageId ? [result.messageId] : [],\n });\n if (!decision.shouldAnswer) {\n console.log(`飞书消息不触发回答:${decision.reason}`);\n }\n }\n },\n });\n}\n\nexport function createFeishuGateway(options: FeishuGatewayOptions): FeishuGatewayRuntime {\n assertFeishuConfig(options.config, options.secrets);\n\n const wsClient =\n options.wsClientFactory?.({\n appId: options.config.feishu.appId,\n appSecret: options.secrets.feishu.appSecret,\n domain: mapDomain(options.config.feishu.domain),\n onReady: () => console.log(\"飞书长连接已建立。\"),\n onError: (error) => console.error(`飞书长连接错误:${error.message}`),\n onReconnecting: () => console.log(\"飞书长连接正在重连。\"),\n onReconnected: () => console.log(\"飞书长连接已重连。\"),\n }) ??\n new lark.WSClient({\n appId: options.config.feishu.appId,\n appSecret: options.secrets.feishu.appSecret,\n domain: mapDomain(options.config.feishu.domain),\n loggerLevel: lark.LoggerLevel.info,\n source: \"chattercatcher\",\n onReady: () => console.log(\"飞书长连接已建立。\"),\n onError: (error) => console.error(`飞书长连接错误:${error.message}`),\n onReconnecting: () => console.log(\"飞书长连接正在重连。\"),\n onReconnected: () => console.log(\"飞书长连接已重连。\"),\n });\n\n const eventDispatcher = createFeishuEventDispatcher({\n config: options.config,\n ingestor: options.ingestor,\n questionHandler: options.questionHandler,\n resourceDownloader: options.resourceDownloader,\n attachmentVectorIndexer: options.attachmentVectorIndexer,\n });\n\n return {\n async start() {\n await wsClient.start({ eventDispatcher });\n },\n stop() {\n wsClient.close({ force: true });\n },\n };\n}\n","import type { Citation, EvidenceSource } from \"./types.js\";\n\nfunction isOpaqueId(value: string | undefined): boolean {\n return Boolean(value && /^(ou|oc|om|cli|on|un|uid)_?[a-z0-9]+/i.test(value));\n}\n\nfunction formatTime(value: string | undefined): string {\n if (!value) {\n return \"未知时间\";\n }\n\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return value;\n }\n\n const pad = (input: number) => String(input).padStart(2, \"0\");\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;\n}\n\nfunction formatSpeaker(source: EvidenceSource): string {\n if (source.type === \"file\") {\n return isOpaqueId(source.label) ? \"文件\" : `文件 ${source.label}`;\n }\n\n if (source.sender && !isOpaqueId(source.sender)) {\n return source.sender;\n }\n\n return \"群成员\";\n}\n\nfunction clipText(value: string, maxLength: number): string {\n const normalized = value.replace(/\\s+/g, \" \").trim();\n return normalized.length > maxLength ? `${normalized.slice(0, maxLength)}...` : normalized;\n}\n\nexport function formatCitation(citation: Citation, options: { maxTextLength?: number } = {}): string {\n const maxTextLength = options.maxTextLength ?? 120;\n const speaker = formatSpeaker(citation.source);\n const time = formatTime(citation.source.timestamp);\n const verb = citation.source.type === \"file\" ? \"记录\" : \"说\";\n return `[${citation.marker}] ${speaker}在 ${time} ${verb}:“${clipText(citation.text, maxTextLength)}”`;\n}\n\nexport function formatCitations(citations: Citation[], options: { maxTextLength?: number } = {}): string {\n return citations.map((citation) => formatCitation(citation, options)).join(\"\\n\");\n}\n","import type { ChatMessage, ChatModel, Citation, EvidenceBlock, GroundedAnswer } from \"./types.js\";\n\nexport interface BuildEvidencePromptOptions {\n maxEvidenceBlocks?: number;\n maxCharsPerBlock?: number;\n}\n\nexport interface EvidencePrompt {\n messages: ChatMessage[];\n citations: Citation[];\n}\n\nconst DEFAULT_MAX_EVIDENCE_BLOCKS = 8;\nconst DEFAULT_MAX_CHARS_PER_BLOCK = 1200;\nconst SCORE_TIE_THRESHOLD = 0.15;\n\nfunction parseTimestamp(value: string | undefined): number {\n if (!value) {\n return 0;\n }\n\n const time = Date.parse(value);\n return Number.isFinite(time) ? time : 0;\n}\n\nexport function rankEvidenceForPrompt(evidence: EvidenceBlock[]): EvidenceBlock[] {\n return [...evidence].sort((left, right) => {\n const scoreDiff = right.score - left.score;\n if (Math.abs(scoreDiff) > SCORE_TIE_THRESHOLD) {\n return scoreDiff;\n }\n\n const timeDiff = parseTimestamp(right.source.timestamp) - parseTimestamp(left.source.timestamp);\n if (timeDiff !== 0) {\n return timeDiff;\n }\n\n return scoreDiff;\n });\n}\n\nexport function buildEvidencePrompt(\n question: string,\n evidence: EvidenceBlock[],\n options: BuildEvidencePromptOptions = {},\n): EvidencePrompt {\n if (evidence.length === 0) {\n throw new Error(\"RAG evidence is required before answer generation.\");\n }\n\n const maxEvidenceBlocks = options.maxEvidenceBlocks ?? DEFAULT_MAX_EVIDENCE_BLOCKS;\n const maxCharsPerBlock = options.maxCharsPerBlock ?? DEFAULT_MAX_CHARS_PER_BLOCK;\n const selected = rankEvidenceForPrompt(evidence).slice(0, maxEvidenceBlocks);\n\n const citations = selected.map<Citation>((item, index) => ({\n marker: `S${index + 1}`,\n evidenceId: item.id,\n source: item.source,\n text: item.text,\n }));\n\n const evidenceText = selected\n .map((item, index) => {\n const marker = citations[index]?.marker;\n const clippedText =\n item.text.length > maxCharsPerBlock ? `${item.text.slice(0, maxCharsPerBlock)}...` : item.text;\n const sourceParts = [\n item.source.label,\n item.source.sender ? `发送人:${item.source.sender}` : undefined,\n item.source.timestamp ? `时间:${item.source.timestamp}` : undefined,\n item.source.location ? `位置:${item.source.location}` : undefined,\n ].filter(Boolean);\n\n return `[${marker}]\\n来源:${sourceParts.join(\";\")}\\n内容:${clippedText}`;\n })\n .join(\"\\n\\n\");\n\n return {\n citations,\n messages: [\n {\n role: \"system\",\n content:\n \"你是 ChatterCatcher 的问答模块。只能根据提供的检索证据回答,必须简短直接。事实性结论必须引用 [S1] 这样的来源标记。证据不足时说不知道,不要猜。若证据互相矛盾,优先采用时间更新且表述明确的证据;如果较新的证据只是讨论、猜测或不确定表达,不要把它当作确定更新。\",\n },\n {\n role: \"user\",\n content: `问题:${question}\\n\\n证据处理规则:\\n1. 先判断证据是否足以回答问题。\\n2. 同一事项出现多个版本时,默认较新的明确消息优先。\\n3. 回答只引用实际支撑结论的证据。\\n\\n检索证据:\\n${evidenceText}`,\n },\n ],\n };\n}\n\nexport async function generateGroundedAnswer(input: {\n question: string;\n evidence: EvidenceBlock[];\n model: ChatModel;\n}): Promise<GroundedAnswer> {\n const prompt = buildEvidencePrompt(input.question, input.evidence);\n const answer = await input.model.complete(prompt.messages);\n\n return {\n answer,\n citations: prompt.citations,\n };\n}\n","import { generateGroundedAnswer } from \"./answer.js\";\nimport type { Retriever } from \"./retriever.js\";\nimport type { ChatModel, GroundedAnswer } from \"./types.js\";\n\nexport interface AskWithRagInput {\n question: string;\n retriever: Retriever;\n model: ChatModel;\n}\n\nexport async function askWithRag(input: AskWithRagInput): Promise<GroundedAnswer> {\n const evidence = await input.retriever.retrieve(input.question);\n\n if (evidence.length === 0) {\n return {\n answer: \"不知道。当前本地知识库没有检索到足够证据。\",\n citations: [],\n };\n }\n\n return generateGroundedAnswer({\n question: input.question,\n evidence,\n model: input.model,\n });\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { formatCitations } from \"../rag/citations.js\";\nimport { createHybridRetriever } from \"../rag/factory.js\";\nimport { askWithRag } from \"../rag/qa-service.js\";\nimport type { ChatModel } from \"../rag/types.js\";\nimport type { MessageSender } from \"./sender.js\";\nimport type { FeishuReceiveMessageEvent } from \"./normalize.js\";\n\nexport interface FeishuQuestionHandlerOptions {\n config: AppConfig;\n secrets: AppSecrets;\n database: SqliteDatabase;\n model: ChatModel;\n sender: MessageSender;\n thinkingEmojiType?: string;\n}\n\nexport interface FeishuQuestionDecision {\n shouldAnswer: boolean;\n question?: string;\n chatId?: string;\n reason?: string;\n}\n\nfunction parseTextContent(content: string | undefined): string {\n if (!content) {\n return \"\";\n }\n\n try {\n const parsed = JSON.parse(content) as { text?: unknown };\n return typeof parsed.text === \"string\" ? parsed.text : \"\";\n } catch {\n return content;\n }\n}\n\nfunction stripMentions(text: string, mentions: NonNullable<NonNullable<FeishuReceiveMessageEvent[\"event\"]>[\"message\"]>[\"mentions\"]): string {\n let result = text;\n\n for (const mention of mentions ?? []) {\n for (const token of [mention.key, mention.name, mention.name ? `@${mention.name}` : undefined]) {\n if (token) {\n result = result.replaceAll(token, \" \");\n }\n }\n }\n\n return result.replace(/@\\s*ChatterCatcher/gi, \" \").replace(/@/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\nexport function isFeishuMessageAddressedToBot(payload: FeishuReceiveMessageEvent): boolean {\n const message = payload.event?.message;\n if (!message || message.message_type !== \"text\") {\n return false;\n }\n\n const mentions = message.mentions ?? [];\n const text = parseTextContent(message.content);\n return mentions.length > 0 || /@?ChatterCatcher/i.test(text);\n}\n\nexport function getFeishuQuestionDecision(\n payload: FeishuReceiveMessageEvent,\n config: AppConfig,\n): FeishuQuestionDecision {\n const message = payload.event?.message;\n if (!message?.chat_id || message.message_type !== \"text\") {\n return {\n shouldAnswer: false,\n reason: \"不是可回答的文本消息。\",\n };\n }\n\n const mentions = message.mentions ?? [];\n const text = parseTextContent(message.content);\n const hasMention = isFeishuMessageAddressedToBot(payload);\n\n if (config.feishu.requireMention && !hasMention) {\n return {\n shouldAnswer: false,\n reason: \"群聊配置为必须 @ 后回答。\",\n };\n }\n\n const question = stripMentions(text, mentions);\n if (!question) {\n return {\n shouldAnswer: false,\n reason: \"没有可回答的问题文本。\",\n };\n }\n\n return {\n shouldAnswer: true,\n question,\n chatId: message.chat_id,\n };\n}\n\nexport class FeishuQuestionHandler {\n constructor(private readonly options: FeishuQuestionHandlerOptions) {}\n\n private async sendResponse(chatId: string, messageId: string | undefined, text: string): Promise<void> {\n if (messageId && this.options.sender.replyTextToMessage) {\n try {\n await this.options.sender.replyTextToMessage(messageId, text);\n return;\n } catch (error) {\n console.log(`飞书回复原消息失败,退回群消息:${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n await this.options.sender.sendTextToChat(chatId, text);\n }\n\n private async acknowledgeQuestion(chatId: string, messageId: string | undefined): Promise<void> {\n if (!messageId) {\n return;\n }\n\n if (this.options.sender.addReactionToMessage) {\n try {\n await this.options.sender.addReactionToMessage(messageId, this.options.thinkingEmojiType ?? \"keyboard\");\n return;\n } catch (error) {\n console.log(`飞书提问表情反馈失败,改用文字反馈:${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n await this.sendResponse(chatId, messageId, \"收到,正在查。\");\n }\n\n async handle(\n payload: FeishuReceiveMessageEvent,\n options: { excludeMessageIds?: string[] } = {},\n ): Promise<FeishuQuestionDecision> {\n const decision = getFeishuQuestionDecision(payload, this.options.config);\n if (!decision.shouldAnswer || !decision.question || !decision.chatId) {\n return decision;\n }\n\n const questionMessageId = payload.event?.message?.message_id;\n await this.acknowledgeQuestion(decision.chatId, questionMessageId);\n\n const { retriever, close } = await createHybridRetriever({\n config: this.options.config,\n secrets: this.options.secrets,\n messages: new MessageRepository(this.options.database),\n excludeMessageIds: options.excludeMessageIds,\n });\n\n try {\n try {\n const result = await askWithRag({\n question: decision.question,\n retriever,\n model: this.options.model,\n });\n const citations = formatCitations(result.citations);\n const text = citations ? `${result.answer}\\n\\n引用:\\n${citations}` : result.answer;\n await this.sendResponse(decision.chatId, questionMessageId, text);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n await this.sendResponse(decision.chatId, questionMessageId, `暂时无法回答:${message}`);\n }\n return decision;\n } finally {\n close();\n }\n }\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\n\nexport interface MessageSender {\n sendTextToChat(chatId: string, text: string): Promise<void>;\n replyTextToMessage?(messageId: string, text: string): Promise<void>;\n addReactionToMessage?(messageId: string, emojiType: string): Promise<void>;\n}\n\ninterface FeishuClientLike {\n im: {\n v1?: {\n message: {\n create(payload: {\n data: {\n receive_id: string;\n msg_type: string;\n content: string;\n };\n params: {\n receive_id_type: \"chat_id\";\n };\n }): Promise<unknown>;\n reply?: (payload: {\n path: {\n message_id: string;\n };\n data: {\n msg_type: string;\n content: string;\n };\n }) => Promise<unknown>;\n };\n messageReaction?: {\n create(payload: {\n path: {\n message_id: string;\n };\n data: {\n reaction_type: {\n emoji_type: string;\n };\n };\n }): Promise<unknown>;\n };\n };\n message?: {\n create(payload: {\n data: {\n receive_id: string;\n msg_type: string;\n content: string;\n };\n params: {\n receive_id_type: \"chat_id\";\n };\n }): Promise<unknown>;\n reply?: (payload: {\n path: {\n message_id: string;\n };\n data: {\n msg_type: string;\n content: string;\n };\n }) => Promise<unknown>;\n };\n };\n}\n\nexport function mapDomain(domain: AppConfig[\"feishu\"][\"domain\"]): lark.Domain {\n return domain === \"lark\" ? lark.Domain.Lark : lark.Domain.Feishu;\n}\n\nexport class FeishuMessageSender implements MessageSender {\n constructor(private readonly client: FeishuClientLike) {}\n\n static fromConfig(config: AppConfig, secrets: AppSecrets): FeishuMessageSender {\n const client = new lark.Client({\n appId: config.feishu.appId,\n appSecret: secrets.feishu.appSecret,\n domain: mapDomain(config.feishu.domain),\n }) as FeishuClientLike;\n\n return new FeishuMessageSender(client);\n }\n\n async sendTextToChat(chatId: string, text: string): Promise<void> {\n const payload = {\n data: {\n receive_id: chatId,\n msg_type: \"text\",\n content: JSON.stringify({ text }),\n },\n params: {\n receive_id_type: \"chat_id\" as const,\n },\n };\n\n if (this.client.im.v1?.message.create) {\n await this.client.im.v1.message.create(payload);\n return;\n }\n\n if (this.client.im.message?.create) {\n await this.client.im.message.create(payload);\n return;\n }\n\n {\n throw new Error(\"当前飞书 SDK 不支持消息发送接口。\");\n }\n }\n\n async replyTextToMessage(messageId: string, text: string): Promise<void> {\n const payload = {\n path: {\n message_id: messageId,\n },\n data: {\n msg_type: \"text\",\n content: JSON.stringify({ text }),\n },\n };\n\n if (this.client.im.v1?.message.reply) {\n await this.client.im.v1.message.reply(payload);\n return;\n }\n\n if (this.client.im.message?.reply) {\n await this.client.im.message.reply(payload);\n return;\n }\n\n throw new Error(\"当前飞书 SDK 不支持消息回复接口。\");\n }\n\n async addReactionToMessage(messageId: string, emojiType: string): Promise<void> {\n if (!this.client.im.v1?.messageReaction?.create) {\n throw new Error(\"当前飞书 SDK 不支持消息表情回复接口。\");\n }\n\n await this.client.im.v1.messageReaction.create({\n path: {\n message_id: messageId,\n },\n data: {\n reaction_type: {\n emoji_type: emojiType,\n },\n },\n });\n }\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport { mapDomain } from \"./sender.js\";\nimport type { FeishuAttachmentMetadata } from \"./normalize.js\";\n\ninterface DownloadResponseLike {\n writeFile(filePath: string): Promise<unknown>;\n}\n\ninterface FeishuResourceClientLike {\n im: {\n v1?: {\n messageResource?: {\n get(payload: {\n params: { type: string };\n path: { message_id: string; file_key: string };\n }): Promise<DownloadResponseLike>;\n };\n };\n messageResource?: {\n get(payload: {\n params: { type: string };\n path: { message_id: string; file_key: string };\n }): Promise<DownloadResponseLike>;\n };\n };\n}\n\nexport interface FeishuDownloadResourceInput {\n messageId: string;\n attachment: FeishuAttachmentMetadata;\n}\n\nexport interface FeishuDownloadedResource {\n messageId: string;\n fileKey: string;\n fileName: string;\n resourceType: string;\n storedPath: string;\n}\n\nconst RESOURCE_TYPE_BY_KIND: Record<FeishuAttachmentMetadata[\"kind\"], string> = {\n file: \"file\",\n image: \"image\",\n audio: \"audio\",\n media: \"media\",\n};\n\nconst DEFAULT_EXTENSION_BY_KIND: Record<FeishuAttachmentMetadata[\"kind\"], string> = {\n file: \".bin\",\n image: \".jpg\",\n audio: \".mp3\",\n media: \".mp4\",\n};\n\nfunction sanitizeFileName(value: string): string {\n const sanitized = value.replace(/[<>:\"/\\\\|?*\\u0000-\\u001f]/g, \"_\").trim();\n return sanitized || \"feishu-resource\";\n}\n\nfunction buildStoredFileName(input: FeishuDownloadResourceInput): string {\n const rawName = input.attachment.fileName || `${input.attachment.fileKey}${DEFAULT_EXTENSION_BY_KIND[input.attachment.kind]}`;\n return `${input.messageId}-${sanitizeFileName(rawName)}`;\n}\n\nexport class FeishuResourceDownloader {\n constructor(\n private readonly client: FeishuResourceClientLike,\n private readonly dataDir: string,\n ) {}\n\n static fromConfig(config: AppConfig, secrets: AppSecrets): FeishuResourceDownloader {\n const client = new lark.Client({\n appId: config.feishu.appId,\n appSecret: secrets.feishu.appSecret,\n domain: mapDomain(config.feishu.domain),\n }) as FeishuResourceClientLike;\n\n return new FeishuResourceDownloader(client, resolveHomePath(config.storage.dataDir));\n }\n\n async download(input: FeishuDownloadResourceInput): Promise<FeishuDownloadedResource> {\n const resourceType = RESOURCE_TYPE_BY_KIND[input.attachment.kind];\n const targetDir = path.join(this.dataDir, \"files\", \"feishu\");\n await fs.mkdir(targetDir, { recursive: true });\n\n const fileName = buildStoredFileName(input);\n const storedPath = path.join(targetDir, fileName);\n const payload = {\n params: { type: resourceType },\n path: { message_id: input.messageId, file_key: input.attachment.fileKey },\n };\n\n const api = this.client.im.v1?.messageResource?.get ?? this.client.im.messageResource?.get;\n if (!api) {\n throw new Error(\"当前飞书 SDK 不支持 messageResource.get,无法下载消息资源。\");\n }\n\n const response = await api(payload);\n await response.writeFile(storedPath);\n\n return {\n messageId: input.messageId,\n fileKey: input.attachment.fileKey,\n fileName,\n resourceType,\n storedPath,\n };\n }\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport { describeSupportedParseTypes, isSupportedParseFile, parseFileToText } from \"./parser.js\";\nimport type { FileJobRepository } from \"./jobs.js\";\n\nexport interface IngestLocalFileResult {\n messageId: string;\n sourcePath: string;\n storedPath: string;\n fileName: string;\n bytes: number;\n characters: number;\n parser: string;\n warnings: string[];\n jobId?: string;\n}\n\nexport function isSupportedTextFile(filePath: string): boolean {\n return isSupportedParseFile(filePath);\n}\n\nfunction ensureSupportedTextFile(filePath: string): void {\n if (!isSupportedTextFile(filePath)) {\n const extension = path.extname(filePath).toLowerCase();\n throw new Error(`暂不支持该文件类型:${extension || \"无扩展名\"}。当前支持 ${describeSupportedParseTypes()}。`);\n }\n}\n\nfunction stableStoredName(sourcePath: string, fileName: string): string {\n const digest = crypto.createHash(\"sha256\").update(sourcePath).digest(\"hex\").slice(0, 16);\n return `${digest}-${fileName}`;\n}\n\nexport async function ingestLocalFile(input: {\n config: AppConfig;\n messages: MessageRepository;\n filePath: string;\n jobs?: FileJobRepository;\n}): Promise<IngestLocalFileResult> {\n const sourcePath = path.resolve(input.filePath);\n const fileName = path.basename(sourcePath);\n const jobId = input.jobs?.start({ sourcePath, fileName });\n\n try {\n ensureSupportedTextFile(sourcePath);\n\n const stat = await fs.stat(sourcePath);\n if (!stat.isFile()) {\n throw new Error(`不是文件:${sourcePath}`);\n }\n\n const parsed = await parseFileToText(sourcePath);\n const text = parsed.text.trim();\n if (!text) {\n throw new Error(`文件没有可索引文本:${sourcePath}`);\n }\n\n const fileDir = path.join(resolveHomePath(input.config.storage.dataDir), \"files\");\n await fs.mkdir(fileDir, { recursive: true });\n\n const storedPath = path.join(fileDir, stableStoredName(sourcePath, fileName));\n await fs.copyFile(sourcePath, storedPath);\n\n const messageId = input.messages.ingest({\n platform: \"local-file\",\n platformChatId: \"local-files\",\n chatName: \"文件库\",\n platformMessageId: sourcePath,\n senderId: \"local-file\",\n senderName: fileName,\n messageType: \"file\",\n text,\n sentAt: stat.mtime.toISOString(),\n rawPayload: {\n sourcePath,\n storedPath,\n bytes: stat.size,\n fileName,\n parser: parsed.parser,\n parserWarnings: parsed.warnings,\n },\n });\n\n input.jobs?.complete({\n id: jobId ?? \"\",\n storedPath,\n parser: parsed.parser,\n messageId,\n bytes: stat.size,\n characters: text.length,\n warnings: parsed.warnings,\n });\n\n return {\n messageId,\n sourcePath,\n storedPath,\n fileName,\n bytes: stat.size,\n characters: text.length,\n parser: parsed.parser,\n warnings: parsed.warnings,\n jobId,\n };\n } catch (error) {\n if (jobId) {\n input.jobs?.fail({\n id: jobId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n throw error;\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport mammoth from \"mammoth\";\nimport { PDFParse } from \"pdf-parse\";\n\nconst TEXT_EXTENSIONS = new Set([\".txt\", \".md\", \".markdown\", \".json\", \".csv\", \".tsv\", \".log\"]);\nconst DOCX_EXTENSIONS = new Set([\".docx\"]);\nconst PDF_EXTENSIONS = new Set([\".pdf\"]);\n\nexport interface ParsedFile {\n text: string;\n parser: \"text\" | \"docx\" | \"pdf\";\n warnings: string[];\n}\n\nexport function isSupportedParseFile(filePath: string): boolean {\n const extension = path.extname(filePath).toLowerCase();\n return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);\n}\n\nexport function describeSupportedParseTypes(): string {\n return \"txt、md、json、csv、tsv、log、docx、pdf\";\n}\n\nexport async function parseFileToText(filePath: string): Promise<ParsedFile> {\n const extension = path.extname(filePath).toLowerCase();\n\n if (TEXT_EXTENSIONS.has(extension)) {\n return {\n text: await fs.readFile(filePath, \"utf8\"),\n parser: \"text\",\n warnings: [],\n };\n }\n\n if (DOCX_EXTENSIONS.has(extension)) {\n const result = await mammoth.extractRawText({ path: filePath });\n return {\n text: result.value,\n parser: \"docx\",\n warnings: result.messages.map((message) => message.message),\n };\n }\n\n if (PDF_EXTENSIONS.has(extension)) {\n const buffer = await fs.readFile(filePath);\n const parser = new PDFParse({ data: buffer });\n try {\n const result = await parser.getText();\n return {\n text: result.text,\n parser: \"pdf\",\n warnings: [],\n };\n } finally {\n await parser.destroy();\n }\n }\n\n throw new Error(`暂不支持该文件类型:${extension || \"无扩展名\"}。当前支持 ${describeSupportedParseTypes()}。`);\n}\n","import type { IngestMessageInput } from \"../messages/types.js\";\n\ntype JsonObject = Record<string, unknown>;\n\nexport interface FeishuAttachmentMetadata {\n platform: \"feishu\";\n kind: \"file\" | \"image\" | \"audio\" | \"media\";\n fileKey: string;\n fileName?: string;\n mimeType?: string;\n size?: number;\n}\n\nexport interface FeishuReceiveMessageEvent {\n event?: {\n sender?: {\n sender_id?: {\n open_id?: string;\n user_id?: string;\n union_id?: string;\n };\n };\n message?: {\n message_id?: string;\n chat_id?: string;\n chat_type?: string;\n create_time?: string;\n message_type?: string;\n content?: string;\n mentions?: Array<{\n name?: string;\n key?: string;\n id?: {\n open_id?: string;\n user_id?: string;\n union_id?: string;\n };\n }>;\n };\n };\n}\n\nfunction asObject(value: unknown): JsonObject {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) ? (value as JsonObject) : {};\n}\n\nfunction parseContent(content: string | undefined): JsonObject {\n if (!content) {\n return {};\n }\n\n try {\n return asObject(JSON.parse(content));\n } catch {\n return { text: content };\n }\n}\n\nfunction stringifyUnknown(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n\n return \"\";\n}\n\nfunction numberUnknown(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value;\n }\n\n if (typeof value === \"string\") {\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n\n return undefined;\n}\n\nfunction extractPostText(content: JsonObject): string {\n const post = asObject(content.post);\n const zhCn = asObject(post.zh_cn ?? post[\"zh-CN\"] ?? post.en_us ?? post[\"en-US\"]);\n const title = stringifyUnknown(zhCn.title);\n const blocks = Array.isArray(zhCn.content) ? zhCn.content : [];\n const parts: string[] = [];\n\n if (title) {\n parts.push(title);\n }\n\n for (const block of blocks) {\n if (!Array.isArray(block)) {\n continue;\n }\n\n for (const item of block) {\n const object = asObject(item);\n const text = stringifyUnknown(object.text);\n if (text) {\n parts.push(text);\n }\n }\n }\n\n return parts.join(\" \").trim();\n}\n\nexport function extractFeishuAttachment(\n messageType: string,\n content: JsonObject,\n): FeishuAttachmentMetadata | undefined {\n if (messageType === \"image\") {\n const fileKey = stringifyUnknown(content.image_key);\n return fileKey ? { platform: \"feishu\", kind: \"image\", fileKey } : undefined;\n }\n\n if (messageType === \"audio\") {\n const fileKey = stringifyUnknown(content.file_key);\n return fileKey ? { platform: \"feishu\", kind: \"audio\", fileKey } : undefined;\n }\n\n if (messageType !== \"file\" && messageType !== \"media\") {\n return undefined;\n }\n\n const fileKey = stringifyUnknown(content.file_key);\n if (!fileKey) {\n return undefined;\n }\n\n return {\n platform: \"feishu\",\n kind: messageType,\n fileKey,\n fileName: stringifyUnknown(content.file_name) || undefined,\n mimeType: stringifyUnknown(content.mime_type) || undefined,\n size: numberUnknown(content.file_size ?? content.size),\n };\n}\n\nfunction extractMessageText(messageType: string, content: JsonObject): string {\n if (messageType === \"text\") {\n return stringifyUnknown(content.text).trim();\n }\n\n if (messageType === \"post\") {\n return extractPostText(content);\n }\n\n if (messageType === \"image\") {\n return `[图片] ${stringifyUnknown(content.image_key)}`.trim();\n }\n\n if (messageType === \"file\") {\n return `[文件] ${stringifyUnknown(content.file_name) || stringifyUnknown(content.file_key)}`.trim();\n }\n\n if (messageType === \"audio\") {\n return `[语音] ${stringifyUnknown(content.file_key)}`.trim();\n }\n\n if (messageType === \"media\") {\n return `[媒体] ${stringifyUnknown(content.file_name) || stringifyUnknown(content.file_key)}`.trim();\n }\n\n const fallback = Object.entries(content)\n .map(([key, value]) => `${key}: ${stringifyUnknown(value)}`)\n .filter((line) => !line.endsWith(\": \"))\n .join(\" \");\n\n return fallback || `[${messageType}]`;\n}\n\nfunction normalizeTimestamp(createTime: string | undefined): string {\n if (!createTime) {\n return new Date().toISOString();\n }\n\n const numeric = Number(createTime);\n if (Number.isFinite(numeric)) {\n const milliseconds = createTime.length <= 10 ? numeric * 1000 : numeric;\n return new Date(milliseconds).toISOString();\n }\n\n const date = new Date(createTime);\n if (Number.isNaN(date.getTime())) {\n return new Date().toISOString();\n }\n\n return date.toISOString();\n}\n\nexport function normalizeFeishuReceiveMessageEvent(payload: FeishuReceiveMessageEvent): IngestMessageInput | null {\n const event = payload.event;\n const message = event?.message;\n if (!event || !message?.message_id || !message.chat_id) {\n return null;\n }\n\n const messageType = message.message_type || \"unknown\";\n const content = parseContent(message.content);\n const text = extractMessageText(messageType, content);\n if (!text) {\n return null;\n }\n\n const senderId =\n event.sender?.sender_id?.open_id ||\n event.sender?.sender_id?.user_id ||\n event.sender?.sender_id?.union_id ||\n \"unknown\";\n\n return {\n platform: \"feishu\",\n platformChatId: message.chat_id,\n chatName: message.chat_id,\n platformMessageId: message.message_id,\n senderId,\n senderName: senderId,\n messageType,\n text,\n sentAt: normalizeTimestamp(message.create_time),\n rawPayload: {\n platform: \"feishu\",\n raw: payload,\n content,\n attachment: extractFeishuAttachment(messageType, content),\n },\n };\n}\n","import type { SqliteDatabase } from \"../db/database.js\";\nimport {\n normalizeFeishuReceiveMessageEvent,\n type FeishuAttachmentMetadata,\n type FeishuReceiveMessageEvent,\n} from \"../feishu/normalize.js\";\nimport type { FeishuDownloadedResource, FeishuResourceDownloader } from \"../feishu/resource-downloader.js\";\nimport { isSupportedTextFile, ingestLocalFile } from \"../files/ingest.js\";\nimport { FileJobRepository } from \"../files/jobs.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport type { IngestMessageInput } from \"../messages/types.js\";\n\nexport interface GatewayIngestResult {\n accepted: boolean;\n messageId?: string;\n message?: IngestMessageInput;\n duplicate?: boolean;\n reason?: string;\n}\n\nexport interface GatewayAttachmentIngestResult {\n downloaded?: FeishuDownloadedResource;\n indexedMessageId?: string;\n vectorIndexed?: {\n chunks: number;\n vectors: number;\n };\n skippedReason?: string;\n}\n\nexport interface GatewayIngestAndDownloadResult extends GatewayIngestResult {\n attachment?: GatewayAttachmentIngestResult;\n}\n\nfunction extractAttachment(message: IngestMessageInput): FeishuAttachmentMetadata | undefined {\n const raw = message.rawPayload;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n return undefined;\n }\n\n const attachment = (raw as { attachment?: unknown }).attachment;\n if (!attachment || typeof attachment !== \"object\" || Array.isArray(attachment)) {\n return undefined;\n }\n\n const candidate = attachment as Partial<FeishuAttachmentMetadata>;\n if (candidate.platform !== \"feishu\" || !candidate.kind || !candidate.fileKey) {\n return undefined;\n }\n\n return candidate as FeishuAttachmentMetadata;\n}\n\nexport class GatewayIngestor {\n private readonly messages: MessageRepository;\n private readonly jobs: FileJobRepository;\n\n constructor(database: SqliteDatabase) {\n this.messages = new MessageRepository(database);\n this.jobs = new FileJobRepository(database);\n }\n\n ingestFeishuEvent(payload: FeishuReceiveMessageEvent): GatewayIngestResult {\n const normalized = normalizeFeishuReceiveMessageEvent(payload);\n if (!normalized) {\n return {\n accepted: false,\n reason: \"事件不是可入库的飞书消息。\",\n };\n }\n\n const duplicate = this.messages.hasPlatformMessage(normalized.platform, normalized.platformMessageId);\n const messageId = this.messages.ingest(normalized);\n return {\n accepted: true,\n messageId,\n message: normalized,\n duplicate,\n };\n }\n\n async ingestFeishuEventAndDownloadAttachments(input: {\n payload: FeishuReceiveMessageEvent;\n downloader: FeishuResourceDownloader;\n config: Parameters<typeof ingestLocalFile>[0][\"config\"];\n vectorIndexMessage?: (messageId: string) => Promise<{ chunks: number; vectors: number }>;\n }): Promise<GatewayIngestAndDownloadResult> {\n const result = this.ingestFeishuEvent(input.payload);\n if (!result.accepted || !result.messageId || !result.message || result.duplicate) {\n return result;\n }\n\n const attachment = extractAttachment(result.message);\n if (!attachment) {\n return result;\n }\n\n const downloaded = await input.downloader.download({\n messageId: result.message.platformMessageId,\n attachment,\n });\n\n if (!isSupportedTextFile(downloaded.storedPath)) {\n return {\n ...result,\n attachment: {\n downloaded,\n skippedReason: \"附件已下载,但当前文件类型暂不支持解析。\",\n },\n };\n }\n\n const indexedMessageId = await ingestLocalFile({\n config: input.config,\n messages: this.messages,\n jobs: this.jobs,\n filePath: downloaded.storedPath,\n }).then((file) => file.messageId);\n const vectorIndexed = input.vectorIndexMessage ? await input.vectorIndexMessage(indexedMessageId) : undefined;\n\n return {\n ...result,\n attachment: {\n downloaded,\n indexedMessageId,\n vectorIndexed,\n },\n };\n }\n}\n","import { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport { getGatewayStatus } from \"./index.js\";\nimport { getGatewayLogPath } from \"./runtime.js\";\n\nexport interface GatewayForegroundSpawnCommand {\n command: string;\n args: string[];\n}\n\nexport interface DetachedGatewayStartInput {\n config: AppConfig;\n secrets: AppSecrets;\n argv?: string[];\n}\n\nexport interface DetachedGatewayStartResult {\n started: boolean;\n message: string;\n pid?: number;\n logFile: string;\n}\n\nexport function buildGatewayForegroundSpawnCommand(argv = process.argv): GatewayForegroundSpawnCommand {\n const [command = process.execPath, ...rawArgs] = argv;\n const args = [...rawArgs];\n\n while (args.at(-1) === \"--foreground\") {\n args.pop();\n }\n\n if (args.at(-1) === \"start\" && args.at(-2) === \"gateway\") {\n args.splice(-2, 2);\n }\n\n return {\n command,\n args: [...args, \"gateway\", \"start\", \"--foreground\"],\n };\n}\n\nexport function startDetachedGateway(input: DetachedGatewayStartInput): DetachedGatewayStartResult {\n const status = getGatewayStatus(input.config, input.secrets);\n const logFile = getGatewayLogPath();\n\n if (status.connection === \"running\") {\n return {\n started: false,\n message: `飞书 Gateway 已经正在运行:pid=${status.pid ?? \"unknown\"}`,\n logFile,\n ...(status.pid ? { pid: status.pid } : {}),\n };\n }\n\n fs.mkdirSync(path.dirname(logFile), { recursive: true });\n\n let out: number | undefined;\n let err: number | undefined;\n let stdioClosed = false;\n const closeStdio = () => {\n if (stdioClosed) {\n return;\n }\n stdioClosed = true;\n if (typeof out === \"number\") {\n fs.closeSync(out);\n }\n if (typeof err === \"number\") {\n fs.closeSync(err);\n }\n };\n\n try {\n out = fs.openSync(logFile, \"a\");\n err = fs.openSync(logFile, \"a\");\n\n const foreground = buildGatewayForegroundSpawnCommand(input.argv);\n const child = spawn(foreground.command, foreground.args, {\n detached: true,\n stdio: [\"ignore\", out, err],\n windowsHide: true,\n });\n\n closeStdio();\n child.unref();\n\n return {\n started: true,\n message: `已在后台启动飞书 Gateway:pid=${child.pid}`,\n pid: child.pid,\n logFile,\n };\n } catch (error) {\n closeStdio();\n throw error;\n }\n}\n","import type { EmbeddingModel } from \"./embedding.js\";\nimport type { VectorRecord, VectorStore } from \"./vector-store.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport type { MessageSearchResult } from \"../messages/types.js\";\n\nexport interface VectorIndexStats {\n chunks: number;\n vectors: number;\n}\n\nexport async function indexMessageChunks(input: {\n messages: MessageRepository;\n embedding: EmbeddingModel;\n store: VectorStore;\n limit?: number;\n messageIds?: string[];\n}): Promise<VectorIndexStats> {\n const chunks = input.messageIds\n ? input.messages.listMessageChunksByMessageIds(input.messageIds, input.limit ?? 10000)\n : input.messages.listAllMessageChunks(input.limit ?? 10000);\n if (chunks.length === 0) {\n return { chunks: 0, vectors: 0 };\n }\n\n const vectors = await input.embedding.embedBatch(chunks.map((chunk) => chunk.text));\n const records: VectorRecord[] = [];\n\n for (const [index, chunk] of chunks.entries()) {\n const vector = vectors[index];\n if (!vector || vector.length === 0) {\n continue;\n }\n\n records.push({\n id: chunk.chunkId,\n vector,\n evidence: {\n id: chunk.chunkId,\n text: chunk.text,\n score: 1,\n source: toEvidenceSource(chunk),\n },\n });\n }\n\n await input.store.upsert(records);\n\n return {\n chunks: chunks.length,\n vectors: records.length,\n };\n}\n\nfunction toEvidenceSource(chunk: MessageSearchResult): VectorRecord[\"evidence\"][\"source\"] {\n if (chunk.messageType === \"file\") {\n return {\n type: \"file\",\n label: chunk.senderName,\n timestamp: chunk.sentAt,\n };\n }\n\n return {\n type: \"message\",\n label: chunk.chatName,\n sender: chunk.senderName,\n timestamp: chunk.sentAt,\n };\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { createEmbeddingModel } from \"../llm/openai-compatible.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { hasEmbeddingConfig } from \"./factory.js\";\nimport { indexMessageChunks } from \"./indexer.js\";\n\nexport interface ManualMessageIndexResult {\n status: \"completed\" | \"skipped\";\n reason?: string;\n chunks: number;\n vectors: number;\n startedAt: string;\n finishedAt: string;\n}\n\nexport async function processMessagesNow(input: {\n config: AppConfig;\n secrets: AppSecrets;\n database: SqliteDatabase;\n limit?: number;\n}): Promise<ManualMessageIndexResult> {\n const startedAt = new Date().toISOString();\n\n if (!hasEmbeddingConfig(input.config, input.secrets)) {\n return {\n status: \"skipped\",\n reason: \"Embedding 配置不完整;SQLite FTS 已在消息入库时即时更新。\",\n chunks: 0,\n vectors: 0,\n startedAt,\n finishedAt: new Date().toISOString(),\n };\n }\n\n const { LanceDbVectorStore } = await import(\"./lancedb-store.js\");\n const vectorStore = await LanceDbVectorStore.connectFromConfig(input.config);\n try {\n const stats = await indexMessageChunks({\n messages: new MessageRepository(input.database),\n embedding: createEmbeddingModel(input.config, input.secrets),\n store: vectorStore,\n limit: input.limit,\n });\n\n return {\n status: \"completed\",\n chunks: stats.chunks,\n vectors: stats.vectors,\n startedAt,\n finishedAt: new Date().toISOString(),\n };\n } finally {\n vectorStore.close();\n }\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\nconst packageName = \"chattercatcher\";\nconst installArgs = [\"install\", \"-g\", `${packageName}@latest`];\nconst latestVersionArgs = [\"view\", packageName, \"version\", \"--json\"];\n\nexport interface UpdateCommandOutput {\n stdout: string;\n stderr: string;\n}\n\nexport type UpdateCommandRunner = (command: string, args: string[]) => Promise<UpdateCommandOutput>;\n\nexport type UpdateResult =\n | { status: \"up-to-date\"; currentVersion: string; latestVersion: string; command: string }\n | { status: \"dry-run\"; currentVersion: string; latestVersion: string; command: string }\n | { status: \"updated\"; currentVersion: string; latestVersion: string; command: string }\n | { status: \"query-failed\"; currentVersion: string; command: string; error: string }\n | { status: \"install-failed\"; currentVersion: string; latestVersion: string; command: string; error: string };\n\nexport interface UpdateOptions {\n currentVersion: string;\n dryRun?: boolean;\n runner?: UpdateCommandRunner;\n}\n\nfunction formatCommand(command: string, args: string[]): string {\n return [command, ...args].join(\" \");\n}\n\nfunction getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n\n return String(error);\n}\n\nfunction parseLatestVersion(stdout: string): string {\n const trimmed = stdout.trim();\n if (!trimmed) {\n throw new Error(\"npm registry returned an empty version\");\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed) as unknown;\n } catch {\n throw new Error(\"npm registry returned an invalid version\");\n }\n\n if (typeof parsed !== \"string\" || !parsed) {\n throw new Error(\"npm registry returned an invalid version\");\n }\n\n return parsed;\n}\n\nfunction compareVersions(left: string, right: string): number {\n const leftParts = left.split(\".\").map((part) => Number(part));\n const rightParts = right.split(\".\").map((part) => Number(part));\n const length = Math.max(leftParts.length, rightParts.length);\n\n for (let index = 0; index < length; index += 1) {\n const leftPart = leftParts[index] ?? 0;\n const rightPart = rightParts[index] ?? 0;\n if (leftPart > rightPart) {\n return 1;\n }\n if (leftPart < rightPart) {\n return -1;\n }\n }\n\n return 0;\n}\n\nexport async function defaultUpdateCommandRunner(command: string, args: string[]): Promise<UpdateCommandOutput> {\n const { stdout, stderr } = await execFileAsync(command, args);\n return { stdout, stderr };\n}\n\nexport async function updateChatterCatcher(options: UpdateOptions): Promise<UpdateResult> {\n const runner = options.runner ?? defaultUpdateCommandRunner;\n const command = formatCommand(\"npm\", installArgs);\n\n let latestVersion: string;\n try {\n const output = await runner(\"npm\", latestVersionArgs);\n latestVersion = parseLatestVersion(output.stdout);\n } catch (error) {\n return {\n status: \"query-failed\",\n currentVersion: options.currentVersion,\n command,\n error: getErrorMessage(error),\n };\n }\n\n if (compareVersions(latestVersion, options.currentVersion) <= 0) {\n return {\n status: \"up-to-date\",\n currentVersion: options.currentVersion,\n latestVersion,\n command,\n };\n }\n\n if (options.dryRun) {\n return {\n status: \"dry-run\",\n currentVersion: options.currentVersion,\n latestVersion,\n command,\n };\n }\n\n try {\n await runner(\"npm\", installArgs);\n } catch (error) {\n return {\n status: \"install-failed\",\n currentVersion: options.currentVersion,\n latestVersion,\n command,\n error: getErrorMessage(error),\n };\n }\n\n return {\n status: \"updated\",\n currentVersion: options.currentVersion,\n latestVersion,\n command,\n };\n}\n","import Fastify, { type FastifyInstance } from \"fastify\";\nimport { loadSecrets } from \"../config/store.js\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { openDatabase } from \"../db/database.js\";\nimport { FileJobRepository } from \"../files/jobs.js\";\nimport { getGatewayStatus } from \"../gateway/index.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { processMessagesNow } from \"../rag/manual-index.js\";\n\nfunction buildHtml(): string {\n return `<!doctype html>\n<html lang=\"zh-CN\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>ChatterCatcher</title>\n <style>\n :root {\n color-scheme: light;\n --bg: #f6f5f0;\n --panel: #ffffff;\n --text: #1f2933;\n --muted: #667085;\n --line: #d9d7cf;\n --accent: #1f7a5a;\n --warn: #9a5b13;\n }\n * { box-sizing: border-box; }\n body {\n margin: 0;\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: var(--bg);\n color: var(--text);\n }\n main { max-width: 1120px; margin: 0 auto; padding: 32px 24px 48px; overflow-x: hidden; }\n header {\n display: flex;\n justify-content: space-between;\n gap: 20px;\n align-items: flex-start;\n padding-bottom: 24px;\n border-bottom: 1px solid var(--line);\n }\n h1 { margin: 0; font-size: 30px; line-height: 1.1; letter-spacing: 0; }\n h2 { margin: 0 0 12px; font-size: 18px; letter-spacing: 0; }\n p { margin: 8px 0 0; color: var(--muted); }\n code { background: #eceae2; border-radius: 4px; padding: 2px 6px; }\n button {\n appearance: none;\n border: 1px solid var(--line);\n background: var(--panel);\n color: var(--text);\n border-radius: 6px;\n padding: 8px 12px;\n cursor: pointer;\n }\n button:hover { border-color: var(--accent); }\n .actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }\n .grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 12px;\n margin: 24px 0;\n }\n .metric {\n background: var(--panel);\n border: 1px solid var(--line);\n border-radius: 8px;\n padding: 16px;\n min-height: 112px;\n }\n .label { color: var(--muted); font-size: 13px; }\n .value { margin-top: 10px; font-size: 22px; font-weight: 650; overflow-wrap: anywhere; line-height: 1.18; }\n .note { margin-top: 8px; color: var(--muted); font-size: 13px; line-height: 1.45; }\n .layout {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(280px, 380px);\n gap: 24px;\n }\n .layout > * { min-width: 0; }\n section { padding: 20px 0; border-top: 1px solid var(--line); }\n section:first-child { border-top: 0; }\n .message-list { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }\n .message-item { padding: 14px 16px; border-bottom: 1px solid var(--line); }\n .message-item:last-child { border-bottom: 0; }\n .message-meta { display: flex; flex-wrap: wrap; gap: 8px 14px; color: var(--muted); font-size: 13px; line-height: 1.4; }\n .message-body { margin-top: 8px; white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.55; }\n table { width: 100%; table-layout: fixed; border-collapse: collapse; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }\n th, td { padding: 12px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; overflow: hidden; text-overflow: ellipsis; }\n th { color: var(--muted); font-size: 13px; font-weight: 600; }\n tr:last-child td { border-bottom: 0; }\n .message { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n .id-text, .path { display: block; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--muted); font-size: 13px; }\n .compact-table th:first-child, .compact-table td:first-child { width: 120px; }\n .compact-table th:nth-child(2), .compact-table td:nth-child(2) { width: 180px; }\n .status-ok { color: var(--accent); }\n .status-warn { color: var(--warn); }\n .empty { color: var(--muted); padding: 18px; background: var(--panel); border: 1px dashed var(--line); border-radius: 8px; }\n .status-line { margin-top: 10px; font-size: 13px; color: var(--muted); text-align: right; }\n @media (max-width: 900px) {\n header, .layout { display: block; }\n .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n header button { margin-top: 16px; }\n }\n @media (max-width: 560px) {\n main { padding: 24px 16px 36px; }\n .grid { grid-template-columns: 1fr; }\n }\n </style>\n </head>\n <body>\n <main>\n <header>\n <div>\n <h1>ChatterCatcher</h1>\n <p>本地优先的家庭群知识库。问答必须先检索 RAG 证据,不堆叠全量上下文。</p>\n </div>\n <div>\n <div class=\"actions\">\n <button id=\"process-messages\" type=\"button\">立即处理</button>\n </div>\n <div id=\"action-status\" class=\"status-line\"></div>\n </div>\n </header>\n\n <div class=\"grid\" id=\"metrics\"></div>\n\n <div class=\"layout\">\n <div>\n <section>\n <h2>最近消息</h2>\n <div id=\"messages\" class=\"empty\">正在读取...</div>\n </section>\n </div>\n <aside>\n <section>\n <h2>群聊</h2>\n <div id=\"chats\" class=\"empty\">正在读取...</div>\n </section>\n <section>\n <h2>文件库</h2>\n <div id=\"files\" class=\"empty\">正在读取...</div>\n </section>\n <section>\n <h2>解析任务</h2>\n <div id=\"file-jobs\" class=\"empty\">正在读取...</div>\n </section>\n <section>\n <h2>本地操作</h2>\n <p><code>chattercatcher settings</code> 修改配置。</p>\n <p><code>chattercatcher files add &lt;path...&gt;</code> 导入文本、DOCX 或 PDF 文件。</p>\n <p><code>chattercatcher doctor</code> 检查飞书、模型、RAG 和本地存储。</p>\n </section>\n </aside>\n </div>\n </main>\n <script>\n const metrics = document.querySelector(\"#metrics\");\n const messages = document.querySelector(\"#messages\");\n const chats = document.querySelector(\"#chats\");\n const files = document.querySelector(\"#files\");\n const fileJobs = document.querySelector(\"#file-jobs\");\n const processMessages = document.querySelector(\"#process-messages\");\n const actionStatus = document.querySelector(\"#action-status\");\n\n function fmt(value) {\n return value == null || value === \"\" ? \"-\" : String(value);\n }\n\n function escapeHtml(value) {\n return fmt(value)\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\");\n }\n\n function isOpaqueId(value) {\n return /^(ou|oc|om|cli|on|un|uid)_?[a-z0-9]+/i.test(fmt(value));\n }\n\n function formatDateTime(value) {\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return fmt(value);\n const pad = (input) => String(input).padStart(2, \"0\");\n return [\n date.getFullYear(),\n pad(date.getMonth() + 1),\n pad(date.getDate()),\n ].join(\"/\") + \" \" + [\n pad(date.getHours()),\n pad(date.getMinutes()),\n pad(date.getSeconds()),\n ].join(\":\");\n }\n\n function displaySender(value) {\n return isOpaqueId(value) ? \"群成员\" : fmt(value);\n }\n\n function displayChatName(value, platform) {\n if (!isOpaqueId(value)) return fmt(value);\n return platform === \"feishu\" ? \"飞书群聊\" : \"群聊\";\n }\n\n function formatGatewayValue(gateway) {\n if (gateway.connection === \"running\") return \"运行中\";\n if (!gateway.configured) return \"未配置\";\n return \"待启动\";\n }\n\n function formatGatewayNote(gateway) {\n if (gateway.connection === \"running\" && gateway.pid) return \"PID \" + gateway.pid;\n return \"飞书长连接\";\n }\n\n function renderMetrics(status) {\n const gatewayClass = status.gateway.configured ? \"status-ok\" : \"status-warn\";\n metrics.innerHTML = [\n [\"Gateway\", formatGatewayValue(status.gateway), formatGatewayNote(status.gateway), gatewayClass],\n [\"群聊\", status.data.chats, \"本地群聊数\", \"\"],\n [\"消息\", status.data.messages, \"已入库消息\", \"\"],\n [\"文件\", status.data.files, \"文件知识源\", \"\"],\n ].map(([label, value, note, extra]) => \\`\n <div class=\"metric\">\n <div class=\"label\">\\${escapeHtml(label)}</div>\n <div class=\"value \\${extra}\">\\${escapeHtml(value)}</div>\n <div class=\"note\">\\${escapeHtml(note)}</div>\n </div>\n \\`).join(\"\");\n }\n\n function renderMessages(items) {\n if (items.length === 0) {\n messages.className = \"empty\";\n messages.textContent = \"还没有消息。启动 Gateway 后,群聊文本会进入本地 RAG 索引。\";\n return;\n }\n messages.className = \"\";\n messages.innerHTML = \\`\n <div class=\"message-list\">\n \\${items.map((item) => \\`\n <article class=\"message-item\">\n <div class=\"message-meta\">\n <span>\\${escapeHtml(formatDateTime(item.sentAt))}</span>\n <span>\\${escapeHtml(displaySender(item.senderName))}</span>\n <span>\\${escapeHtml(displayChatName(item.chatName, item.platform))}</span>\n </div>\n <div class=\"message-body\">\\${escapeHtml(item.text)}</div>\n </article>\n \\`).join(\"\")}\n </div>\n \\`;\n }\n\n function renderChats(items) {\n if (items.length === 0) {\n chats.className = \"empty\";\n chats.textContent = \"还没有群聊记录。\";\n return;\n }\n chats.className = \"\";\n chats.innerHTML = \\`\n <table>\n <thead><tr><th>名称</th><th>平台</th></tr></thead>\n <tbody>\n \\${items.map((item) => \\`\n <tr>\n <td><span class=\"id-text\" title=\"\\${escapeHtml(item.name)}\">\\${escapeHtml(displayChatName(item.name, item.platform))}</span></td>\n <td>\\${escapeHtml(item.platform)}</td>\n </tr>\n \\`).join(\"\")}\n </tbody>\n </table>\n \\`;\n }\n\n function renderFiles(items) {\n if (items.length === 0) {\n files.className = \"empty\";\n files.textContent = \"还没有文件。可先运行 chattercatcher files add <path...> 导入文本、DOCX 或 PDF 文件。\";\n return;\n }\n files.className = \"\";\n files.innerHTML = \\`\n <table>\n <thead><tr><th>文件</th><th>解析器</th><th>字符</th></tr></thead>\n <tbody>\n \\${items.map((item) => \\`\n <tr>\n <td>\n <div>\\${escapeHtml(item.fileName)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.storedPath)}\">\\${escapeHtml(item.storedPath)}</div>\n </td>\n <td>\\${escapeHtml(item.parser || \"unknown\")}</td>\n <td>\\${escapeHtml(item.characters)}</td>\n </tr>\n \\`).join(\"\")}\n </tbody>\n </table>\n \\`;\n }\n\n function renderFileJobs(items) {\n if (items.length === 0) {\n fileJobs.className = \"empty\";\n fileJobs.textContent = \"还没有文件解析任务。\";\n return;\n }\n fileJobs.className = \"\";\n fileJobs.innerHTML = \\`\n <table>\n <thead><tr><th>任务</th><th>状态</th></tr></thead>\n <tbody>\n \\${items.map((item) => \\`\n <tr>\n <td>\n <div>\\${escapeHtml(item.fileName)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.id)}\">ID: \\${escapeHtml(item.id)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.error || item.storedPath)}\">\\${escapeHtml(item.error || item.storedPath)}</div>\n </td>\n <td>\\${escapeHtml(item.status)}</td>\n </tr>\n \\`).join(\"\")}\n </tbody>\n </table>\n \\`;\n }\n\n async function load() {\n const [status, recent, chatList, fileList, jobList] = await Promise.all([\n fetch(\"/api/status\").then((response) => response.json()),\n fetch(\"/api/messages/recent?limit=20\").then((response) => response.json()),\n fetch(\"/api/chats\").then((response) => response.json()),\n fetch(\"/api/files\").then((response) => response.json()),\n fetch(\"/api/file-jobs\").then((response) => response.json()),\n ]);\n renderMetrics(status);\n renderMessages(recent.items);\n renderChats(chatList.items);\n renderFiles(fileList.items);\n renderFileJobs(jobList.items);\n }\n\n async function processNow() {\n processMessages.disabled = true;\n actionStatus.textContent = \"正在处理消息索引...\";\n try {\n const response = await fetch(\"/api/process/messages\", { method: \"POST\" });\n const result = await response.json();\n if (!response.ok) {\n actionStatus.textContent = result.message || \"处理失败。\";\n return;\n }\n\n if (result.status === \"skipped\") {\n actionStatus.textContent = result.reason;\n } else {\n actionStatus.textContent = \\`处理完成:chunks=\\${result.chunks}, vectors=\\${result.vectors}\\`;\n }\n await load();\n } catch (error) {\n actionStatus.textContent = error instanceof Error ? error.message : String(error);\n } finally {\n processMessages.disabled = false;\n }\n }\n\n processMessages.addEventListener(\"click\", () => void processNow());\n void load();\n setInterval(() => {\n if (document.visibilityState === \"visible\") {\n void load();\n }\n }, 5000);\n </script>\n </body>\n</html>`;\n}\n\nfunction parseLimit(value: string | undefined, fallback: number, max: number): number {\n const rawLimit = Number(value ?? fallback);\n return Number.isFinite(rawLimit) ? Math.min(Math.max(Math.trunc(rawLimit), 1), max) : fallback;\n}\n\nexport function createWebApp(config: AppConfig): FastifyInstance {\n const app = Fastify({ logger: false });\n const database = openDatabase(config);\n const messages = new MessageRepository(database);\n const fileJobs = new FileJobRepository(database);\n\n app.addHook(\"onClose\", async () => {\n database.close();\n });\n\n app.get(\"/api/status\", async () => ({\n app: \"ChatterCatcher\",\n gateway: getGatewayStatus(config),\n data: {\n chats: messages.getChatCount(),\n messages: messages.getMessageCount(),\n files: messages.listFiles(1_000).length,\n },\n rag: {\n mode: \"required\",\n note: \"问答必须先检索证据,禁止全量上下文堆叠。\",\n retrieval: {\n keyword: \"SQLite FTS5\",\n vector: \"LanceDB\",\n hybrid: true,\n },\n },\n web: config.web,\n }));\n\n app.get(\"/api/chats\", async () => ({\n items: messages.listChats(),\n }));\n\n app.get(\"/api/files\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 50, 200);\n return {\n items: messages.listFiles(limit),\n };\n });\n\n app.get(\"/api/file-jobs\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 50, 200);\n const status = (request.query as { status?: string }).status;\n return {\n items: fileJobs.list(limit, status === \"processing\" || status === \"indexed\" || status === \"failed\" ? { status } : {}),\n };\n });\n\n app.get(\"/api/messages/recent\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 20, 100);\n return {\n items: messages.listRecentMessages(limit),\n };\n });\n\n app.post(\"/api/process/messages\", async (_request, reply) => {\n try {\n return await processMessagesNow({\n config,\n secrets: await loadSecrets(),\n database,\n limit: 10_000,\n });\n } catch (error) {\n reply.code(500);\n return {\n status: \"failed\",\n message: error instanceof Error ? error.message : String(error),\n };\n }\n });\n\n app.get(\"/\", async (_request, reply) => {\n reply.type(\"text/html; charset=utf-8\");\n return buildHtml();\n });\n\n return app;\n}\n\nexport async function startWebServer(config: AppConfig): Promise<void> {\n const app = createWebApp(config);\n await app.listen({ host: config.web.host, port: config.web.port });\n const address = app.server.address();\n const url =\n typeof address === \"string\" ? address : `http://${config.web.host}:${address?.port ?? config.web.port}`;\n console.log(`ChatterCatcher Web UI: ${url}`);\n}\n"],"mappings":"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","names":["os","path","fs","path","data","fs","path","path","input","fs","path","input","deleted","fs","path","fs","path","input","fs","path","fs","path","files","input","path","fs","data","crypto","nowIso","crypto","index","input","input","LanceDbVectorStore","fs","getLanceDbPath","LanceDbVectorStore","fs","path","input","fs","path","data","input","lark","input","index","input","input","data","lark","fs","path","input","path","fs","crypto","fs","path","fs","path","path","crypto","input","fs","asObject","fileKey","input","fs","path","input","fs","path","input","index","toEvidenceSource","input","LanceDbVectorStore","index","LanceDbVectorStore","getLanceDbPath","files","fs"]}