chattercatcher 0.1.26 → 0.1.27

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","../package.json","../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/logs/reader.ts","../src/gateway/index.ts","../src/llm/openai-compatible.ts","../src/messages/repository.ts","../src/messages/chunker.ts","../src/episodes/repository.ts","../src/episodes/sanitizer.ts","../src/rag/episode-retriever.ts","../src/rag/hybrid-retriever.ts","../src/rag/message-retriever.ts","../src/rag/search-tools.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/episodes/summarizer.ts","../src/episodes/manual-process.ts","../src/feishu/bot-info.ts","../src/feishu/gateway.ts","../src/cron/jobs.ts","../src/cron/schedule.ts","../src/cron/generator.ts","../src/cron/scheduler.ts","../src/gateway/indexing-scheduler.ts","../src/rag/indexer.ts","../src/rag/manual-index.ts","../src/multimodal/tasks.ts","../src/multimodal/worker.ts","../src/cron/tools.ts","../src/rag/qa-logs.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/multimodal/openai-compatible.ts","../src/rag/answer.ts","../src/rag/qa-service.ts","../src/rag/citations.ts","../src/update/npm-updater.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 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 { processEpisodesNow } from \"./episodes/manual-process.js\";\nimport { ensureFeishuBotOpenId } from \"./feishu/bot-info.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 { createMultimodalModel } from \"./multimodal/openai-compatible.js\";\nimport { followLogFile, getLogsDirectory, normalizeLineCount, readLatestLogTail } from \"./logs/reader.js\";\nimport { MessageRepository } from \"./messages/repository.js\";\nimport { indexMessageChunks } from \"./rag/indexer.js\";\nimport { createHybridRetriever, hasEmbeddingConfig } from \"./rag/factory.js\";\nimport { processMessagesNow } from \"./rag/manual-index.js\";\nimport { SqliteVectorStore } from \"./rag/sqlite-vector-store.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 await tryEnsureFeishuBotOpenId(config, secrets);\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 multimodalBaseUrl = await input({\n message: \"Multimodal Base URL(OpenAI-compatible,可留空)\",\n default: config.multimodal.baseUrl,\n });\n const multimodalApiKey = await password({\n message: \"Multimodal API Key(可留空)\",\n mask: \"*\",\n });\n const multimodalModel = await input({\n message: \"Multimodal Model(可留空)\",\n default: config.multimodal.model,\n });\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 config.multimodal = {\n baseUrl: multimodalBaseUrl,\n model: multimodalModel,\n };\n\n secrets.multimodal = {\n apiKey: multimodalApiKey || secrets.multimodal.apiKey,\n };\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: \"群聊回答是否要求 @ 机器人?\",\n default: config.feishu.requireMention,\n });\n config.episodes.windowMinutes =\n (await number({ message: \"会话记忆聚合窗口(分钟)\", default: config.episodes.windowMinutes, required: true })) ??\n config.episodes.windowMinutes;\n config.episodes.quietMinutes =\n (await number({ message: \"会话静默多久后生成记忆(分钟)\", default: config.episodes.quietMinutes, required: true })) ??\n config.episodes.quietMinutes;\n}\n\nasync function tryEnsureFeishuBotOpenId(config: AppConfig, secrets: AppSecrets): Promise<void> {\n if (config.feishu.botOpenId || !config.feishu.appId || !secrets.feishu.appSecret) {\n return;\n }\n\n try {\n const openId = await ensureFeishuBotOpenId(config, secrets, { onSave: () => saveConfig(config) });\n console.log(`已自动获取飞书机器人 Open ID:${openId}`);\n } catch (error) {\n console.log(`暂时无法自动获取飞书机器人 Open ID:${error instanceof Error ? error.message : String(error)}`);\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 multimodal: { apiKey: maskSecret(secrets.multimodal.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, { version: packageJson.version });\n return;\n }\n\n await tryEnsureFeishuBotOpenId(config, secrets);\n\n writeGatewayPidRecord(undefined, {\n ...pidRecordBase,\n mode: \"gateway\",\n });\n\n const database = openDatabase(config);\n const chatModel = createChatModel(config, secrets);\n const sender = FeishuMessageSender.fromConfig(config, secrets);\n const vectorStore = hasEmbeddingConfig(config, secrets)\n ? new SqliteVectorStore(database, { model: config.embedding.model })\n : 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 episodeProcessor: {\n database,\n model: chatModel,\n },\n imageMultimodalProcessor:\n config.multimodal.baseUrl && config.multimodal.model && secrets.multimodal.apiKey\n ? {\n database,\n model: createMultimodalModel(config, secrets),\n }\n : undefined,\n indexingProcessor: {\n database,\n },\n cronJobProcessor: {\n database,\n model: chatModel,\n sender,\n },\n questionHandler: new FeishuQuestionHandler({\n config,\n secrets,\n database,\n sender,\n model: chatModel,\n }),\n });\n\n const cleanup = () => {\n gatewayRuntime.stop();\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, { version: packageJson.version });\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 await tryEnsureFeishuBotOpenId(config, secrets);\n const result = await 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, { version: packageJson.version });\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 已同步删除;如使用 SQLite embedding 语义检索,请运行 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\n try {\n const messages = new MessageRepository(database);\n const vectorStore = new SqliteVectorStore(database, { model: config.embedding.model });\n const vectors = vectorStore.count();\n console.log(JSON.stringify(\n {\n database: getDatabasePath(config),\n chats: messages.getChatCount(),\n messages: messages.getMessageCount(),\n embeddings: {\n backend: \"SQLite embedding 向量索引\",\n configured: hasEmbeddingConfig(config, secrets),\n model: config.embedding.model,\n vectors,\n status: hasEmbeddingConfig(config, secrets)\n ? \"SQLite embedding 向量索引已可用于语义检索\"\n : \"SQLite embedding 向量索引已接入;需配置 embedding 后启用语义检索\",\n },\n retrieval: {\n keyword: \"SQLite FTS5\",\n vector: \"SQLite embedding 向量索引\",\n hybrid: \"启用:SQLite FTS + SQLite embedding 向量检索\",\n rag: \"强制先检索证据再回答,禁止全量上下文堆叠\",\n },\n },\n null,\n 2,\n ));\n } finally {\n database.close();\n }\n});\n\nindex.command(\"rebuild\").description(\"重建语义向量索引\").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 配置不完整,无法重建 SQLite embedding 向量索引。请运行 chattercatcher setup 或 chattercatcher settings。\");\n return;\n }\n\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\n if (result.status === \"skipped\") {\n console.log(`处理跳过:${result.reason}`);\n return;\n }\n\n console.log(`SQLite embedding 向量索引完成:chunks=${result.chunks}, vectors=${result.vectors}`);\n } finally {\n database.close();\n }\n});\n\nconst processCommand = program.command(\"process\").description(\"立即处理后台任务\");\n\nprocessCommand\n .command(\"messages\")\n .description(\"立即处理消息索引任务,把消息 chunks 写入 SQLite embedding 向量索引\")\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\nprocessCommand\n .command(\"episodes\")\n .description(\"立即生成会话记忆块,把碎片化闲聊整理成可检索摘要\")\n .action(async () => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n\n try {\n const result = await processEpisodesNow({\n config,\n secrets,\n database,\n model: createChatModel(config, secrets),\n });\n console.log(`会话记忆处理完成:episodes=${result.created}`);\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 embedding 向量索引。\");\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 已重建;如使用 SQLite embedding 语义检索,请运行 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 database,\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 database,\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.26\",\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/DEVELOPMENT_PLAN.md\",\n \"docs/PRD.md\",\n \"docs/TECHNICAL_ARCHITECTURE.md\",\n \"README.md\",\n \"AGENTS.md\"\n ],\n \"directories\": {\n \"doc\": \"docs\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"prepack\": \"npm run build\",\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 \"@larksuiteoapi/node-sdk\": \"^1.62.1\",\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 botOpenId: 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 multimodal: z.preprocess(\n (value) => value ?? {},\n z.object({\n baseUrl: z.string().url().or(z.literal(\"\")).default(\"\"),\n model: z.string().default(\"\"),\n }),\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 episodes: z\n .object({\n windowMinutes: z.number().int().positive().default(10),\n quietMinutes: z.number().int().positive().default(2),\n })\n .default({ windowMinutes: 10, quietMinutes: 2 }),\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 multimodal: z.preprocess(\n (value) => value ?? {},\n z.object({\n apiKey: z.string().default(\"\"),\n }),\n ),\n web: z.preprocess(\n (value) => value ?? {},\n z.object({\n actionToken: z.string().default(\"\"),\n }),\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 multimodal: {},\n storage: {},\n web: {},\n schedules: {},\n episodes: {},\n });\n}\n\nexport function createDefaultSecrets(): AppSecrets {\n return appSecretsSchema.parse({\n feishu: {},\n llm: {},\n embedding: {},\n multimodal: {},\n web: {},\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 memory_episodes (\n id TEXT PRIMARY KEY,\n chat_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,\n summary TEXT NOT NULL,\n message_count INTEGER NOT NULL,\n started_at TEXT NOT NULL,\n ended_at TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE(chat_id, started_at, ended_at)\n );\n\n CREATE TABLE IF NOT EXISTS memory_episode_messages (\n episode_id TEXT NOT NULL REFERENCES memory_episodes(id) ON DELETE CASCADE,\n message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n position INTEGER NOT NULL,\n PRIMARY KEY (episode_id, message_id)\n );\n\n CREATE VIRTUAL TABLE IF NOT EXISTS memory_episodes_fts USING fts5(\n summary,\n episode_id UNINDEXED,\n tokenize = 'unicode61'\n );\n\n CREATE TRIGGER IF NOT EXISTS memory_episodes_delete_fts\n AFTER DELETE ON memory_episodes\n BEGIN\n DELETE FROM memory_episodes_fts WHERE episode_id = old.id;\n END;\n\n CREATE INDEX IF NOT EXISTS memory_episode_messages_message_idx\n ON memory_episode_messages(message_id);\n\n CREATE TABLE IF NOT EXISTS message_chunk_embeddings (\n chunk_id TEXT NOT NULL REFERENCES message_chunks(id) ON DELETE CASCADE,\n model TEXT NOT NULL,\n dimension INTEGER NOT NULL,\n embedding_json TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (chunk_id, model)\n );\n\n CREATE INDEX IF NOT EXISTS message_chunk_embeddings_model_idx\n ON message_chunk_embeddings(model, dimension);\n\n CREATE TABLE IF NOT EXISTS qa_logs (\n id TEXT PRIMARY KEY,\n chat_id TEXT,\n question_message_id TEXT,\n question TEXT NOT NULL,\n answer TEXT NOT NULL,\n citations_json TEXT NOT NULL,\n retrieval_debug_json TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('answered','failed')),\n error TEXT,\n created_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS qa_logs_created_at_idx ON qa_logs(created_at);\n CREATE INDEX IF NOT EXISTS qa_logs_chat_idx ON qa_logs(chat_id, created_at);\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 CREATE TABLE IF NOT EXISTS image_multimodal_tasks (\n id TEXT PRIMARY KEY,\n source_message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n platform_message_id TEXT NOT NULL,\n image_key TEXT NOT NULL,\n stored_path TEXT NOT NULL,\n mime_type TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('pending','running','succeeded','skipped','failed')),\n attempts INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n derived_message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE(source_message_id, image_key)\n );\n\n CREATE INDEX IF NOT EXISTS image_multimodal_tasks_status_idx ON image_multimodal_tasks(status, updated_at);\n\n CREATE TABLE IF NOT EXISTS cron_jobs (\n id TEXT PRIMARY KEY,\n chat_id TEXT NOT NULL,\n created_by_open_id TEXT,\n schedule TEXT NOT NULL,\n prompt TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('active','deleted')),\n last_run_at TEXT,\n next_run_at TEXT NOT NULL,\n last_error TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS cron_jobs_chat_status_idx ON cron_jobs(chat_id, status, updated_at);\n CREATE INDEX IF NOT EXISTS cron_jobs_due_idx ON cron_jobs(status, next_run_at);\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 checkSqliteVectorIndex(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 embedding 语义检索。\");\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 checkSqliteVectorIndex(config: AppConfig): Promise<DoctorCheck> {\n let database: ReturnType<typeof openDatabase> | null = null;\n try {\n database = openDatabase(config);\n const defaultModel = config.embedding.model || \"default\";\n const vectorStore = new SqliteVectorStore(database, { model: defaultModel });\n const vectors = vectorStore.count();\n const availableModels = database\n .prepare(\"SELECT COUNT(DISTINCT model) AS count FROM message_chunk_embeddings\")\n .get() as { count: number };\n\n return pass(\n \"SQLite embedding 向量索引\",\n `${getDatabasePath(config)};vectors=${vectors};models=${availableModels.count}${config.embedding.model ? `;active_model=${config.embedding.model}` : \";active_model=未配置\"}`,\n );\n } catch (error) {\n return fail(\"SQLite embedding 向量索引\", 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\";\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, ChatTool, ToolCall, ToolChatResult } from \"../rag/types.js\";\n\nexport interface OpenAICompatibleChatOptions {\n baseUrl: string;\n apiKey: string;\n model: string;\n temperature?: number;\n}\n\ninterface OpenAICompatibleMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string;\n tool_call_id?: string;\n tool_calls?: Array<{\n id: string;\n type: \"function\";\n function: {\n name: string;\n arguments: string;\n };\n }>;\n}\n\ninterface ChatCompletionResponse {\n choices?: Array<{\n message?: OpenAICompatibleMessage & { reasoning_content?: string };\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\nfunction toOpenAIMessage(message: ChatMessage): OpenAICompatibleMessage {\n return {\n role: message.role,\n content: message.content,\n ...(message.toolCallId ? { tool_call_id: message.toolCallId } : {}),\n ...(message.toolCalls\n ? {\n tool_calls: message.toolCalls.map((toolCall) => ({\n id: toolCall.id,\n type: \"function\" as const,\n function: {\n name: toolCall.name,\n arguments: JSON.stringify(toolCall.input),\n },\n })),\n }\n : {}),\n ...(message.reasoningContent ? { reasoning_content: message.reasoningContent } : {}),\n };\n}\n\nfunction toOpenAITool(tool: ChatTool): {\n type: \"function\";\n function: {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n };\n} {\n return {\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.inputSchema,\n },\n };\n}\n\nfunction parseToolCalls(message?: OpenAICompatibleMessage): ToolCall[] {\n return (\n message?.tool_calls?.map((toolCall) => ({\n id: toolCall.id,\n name: toolCall.function.name,\n input: JSON.parse(toolCall.function.arguments),\n })) ?? []\n );\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: messages.map(toOpenAIMessage),\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 message = data.choices?.[0]?.message;\n const content = message?.content?.trim();\n if (!content) {\n throw new Error(\"LLM 返回为空。\");\n }\n\n return content;\n }\n\n async completeWithTools(messages: ChatMessage[], tools: ChatTool[]): Promise<ToolChatResult> {\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: messages.map(toOpenAIMessage),\n tools: tools.map(toOpenAITool),\n tool_choice: \"auto\",\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 message = data.choices?.[0]?.message;\n\n return {\n content: message?.content ?? \"\",\n toolCalls: parseToolCalls(message),\n reasoningContent: message?.reasoning_content ?? undefined,\n };\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 {\n ChatRecord,\n CreateImageSummaryMessageInput,\n FileRecord,\n IngestMessageInput,\n MessageSearchResult,\n MessageSearchScope,\n} 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 buildScopeWhere(scope?: MessageSearchScope): { where: string; params: string[] } {\n const clauses: string[] = [];\n const params: string[] = [];\n if (scope?.platform) {\n clauses.push(\"m.platform = ?\");\n params.push(scope.platform);\n }\n if (scope?.platformChatId) {\n clauses.push(\"c.platform_chat_id = ?\");\n params.push(scope.platformChatId);\n }\n\n return {\n where: clauses.length > 0 ? `AND ${clauses.join(\" AND \")}` : \"\",\n params,\n };\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 message_type = excluded.message_type,\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 createImageSummaryMessage(input: CreateImageSummaryMessageInput): string {\n const source = this.database\n .prepare(\n `\n SELECT\n m.platform AS platform,\n m.platform_message_id AS platformMessageId,\n m.chat_id AS chatId,\n m.sender_id AS senderId,\n m.sender_name AS senderName,\n m.sent_at AS sentAt,\n c.platform_chat_id AS platformChatId,\n c.name AS chatName\n FROM messages m\n JOIN chats c ON c.id = m.chat_id\n WHERE m.id = ?\n `,\n )\n .get(input.sourceMessageId) as\n | {\n platform: string;\n platformMessageId: string;\n chatId: string;\n senderId: string;\n senderName: string;\n sentAt: string;\n platformChatId: string;\n chatName: string;\n }\n | undefined;\n\n if (!source) {\n throw new Error(\"原始图片消息不存在。\");\n }\n\n const derivedPlatformMessageId = `${source.platformMessageId}:image-summary:${input.imageKey}`;\n return this.ingest({\n platform: source.platform,\n platformChatId: source.platformChatId,\n chatName: source.chatName,\n platformMessageId: derivedPlatformMessageId,\n senderId: source.senderId,\n senderName: source.senderName,\n messageType: \"image_summary\",\n text: `[图片转述] ${input.summary.trim()}`,\n sentAt: source.sentAt,\n rawPayload: {\n derivedFromMessageId: input.sourceMessageId,\n sourceAttachmentKind: \"image\",\n sourceResourceKey: input.imageKey,\n multimodalModel: input.multimodalModel,\n isMeaningful: true,\n ...(input.reason?.trim() ? { reason: input.reason.trim() } : {}),\n generatedAt: input.generatedAt,\n },\n });\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[]; scope?: MessageSearchScope } = {}): 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 scope = buildScopeWhere(options.scope);\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 ${scope.where}\n ORDER BY bm25(message_chunks_fts)\n LIMIT ?\n `,\n )\n .all(ftsQuery, ...excludedIds, ...scope.params, 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 ${scope.where}\n ORDER BY m.sent_at DESC\n LIMIT ?\n `,\n )\n .all(...params, ...excludedIds, ...scope.params, 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 crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport type { MessageSearchResult, MessageSearchScope } from \"../messages/types.js\";\nimport { sanitizeEpisodeSummary } from \"./sanitizer.js\";\n\nexport interface EpisodeMessage {\n id: string;\n chatId: string;\n chatName: string;\n senderName: string;\n text: string;\n sentAt: string;\n}\n\nexport interface EpisodeWindow {\n chatId: string;\n chatName: string;\n startedAt: string;\n endedAt: string;\n messages: EpisodeMessage[];\n}\n\nexport interface EpisodeSummaryRecord {\n id: string;\n chatId: string;\n chatName: string;\n text: string;\n startedAt: string;\n endedAt: string;\n messageIds: string[];\n}\n\nexport interface EpisodeSearchResult extends MessageSearchResult {\n sourceMessageIds: string[];\n startedAt: string;\n endedAt: string;\n}\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(/[^\\p{L}\\p{N}_-]+/gu, \" \").trim())\n .flatMap((term) => term.split(/\\s+/))\n .filter(Boolean);\n\n if (terms.length === 0) {\n return \"\\\"\\\"\";\n }\n\n return terms.map((term) => `\"${term.replace(/\"/g, '\"\"')}\"`).join(\" OR \");\n}\n\nfunction toMillis(value: string): number {\n const time = Date.parse(value);\n return Number.isFinite(time) ? time : 0;\n}\n\nfunction buildScopeWhere(scope?: MessageSearchScope): { where: string; params: string[] } {\n const clauses: string[] = [];\n const params: string[] = [];\n if (scope?.platform) {\n clauses.push(\"c.platform = ?\");\n params.push(scope.platform);\n }\n if (scope?.platformChatId) {\n clauses.push(\"c.platform_chat_id = ?\");\n params.push(scope.platformChatId);\n }\n\n return {\n where: clauses.length > 0 ? `AND ${clauses.join(\" AND \")}` : \"\",\n params,\n };\n}\n\nexport interface EpisodeListItem {\n id: string;\n chatId: string;\n chatName: string;\n summary: string;\n messageCount: number;\n startedAt: string;\n endedAt: string;\n createdAt: string;\n}\n\nexport class EpisodeRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n async summarizeReadyWindows(input: {\n now: Date;\n quietMs: number;\n windowMs: number;\n summarize: (window: EpisodeWindow, now: Date) => Promise<string>;\n }): Promise<EpisodeSummaryRecord[]> {\n const rows = this.database\n .prepare(\n `\n SELECT\n m.id,\n m.chat_id AS chatId,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.text,\n m.sent_at AS sentAt\n FROM messages m\n JOIN chats c ON c.id = m.chat_id\n WHERE NOT EXISTS (\n SELECT 1 FROM memory_episode_messages mem WHERE mem.message_id = m.id\n )\n ORDER BY m.chat_id ASC, m.sent_at ASC\n `,\n )\n .all() as EpisodeMessage[];\n\n const byChat = new Map<string, EpisodeMessage[]>();\n for (const row of rows) {\n byChat.set(row.chatId, [...(byChat.get(row.chatId) ?? []), row]);\n }\n\n const created: EpisodeSummaryRecord[] = [];\n const nowMs = input.now.getTime();\n for (const messages of byChat.values()) {\n const windows: EpisodeMessage[][] = [];\n let current: EpisodeMessage[] = [];\n for (const message of messages) {\n const first = current[0];\n if (first && toMillis(message.sentAt) - toMillis(first.sentAt) > input.windowMs) {\n windows.push(current);\n current = [];\n }\n current.push(message);\n }\n if (current.length > 0) {\n windows.push(current);\n }\n\n for (const windowMessages of windows) {\n const last = windowMessages.at(-1);\n if (!last || nowMs - toMillis(last.sentAt) < input.quietMs) {\n continue;\n }\n\n const first = windowMessages[0]!;\n const window: EpisodeWindow = {\n chatId: first.chatId,\n chatName: first.chatName,\n startedAt: first.sentAt,\n endedAt: last.sentAt,\n messages: windowMessages,\n };\n const summary = await input.summarize(window, input.now);\n created.push(this.insertEpisode(window, summary));\n }\n }\n\n return created;\n }\n\n private insertEpisode(window: EpisodeWindow, summary: string): EpisodeSummaryRecord {\n const safeSummary = sanitizeEpisodeSummary(summary);\n const createdAt = nowIso();\n const id = stableId([window.chatId, window.startedAt, window.endedAt]);\n const transaction = this.database.transaction(() => {\n this.database\n .prepare(\n `\n INSERT INTO memory_episodes (id, chat_id, summary, message_count, started_at, ended_at, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(chat_id, started_at, ended_at)\n DO UPDATE SET summary = excluded.summary, message_count = excluded.message_count\n `,\n )\n .run(id, window.chatId, safeSummary, window.messages.length, window.startedAt, window.endedAt, createdAt);\n this.database.prepare(\"DELETE FROM memory_episode_messages WHERE episode_id = ?\").run(id);\n this.database.prepare(\"DELETE FROM memory_episodes_fts WHERE episode_id = ?\").run(id);\n\n const insertMessage = this.database.prepare(\n \"INSERT INTO memory_episode_messages (episode_id, message_id, position) VALUES (?, ?, ?)\",\n );\n for (const [index, message] of window.messages.entries()) {\n insertMessage.run(id, message.id, index);\n }\n this.database.prepare(\"INSERT INTO memory_episodes_fts (summary, episode_id) VALUES (?, ?)\").run(safeSummary, id);\n });\n\n transaction();\n return {\n id,\n chatId: window.chatId,\n chatName: window.chatName,\n text: safeSummary,\n startedAt: window.startedAt,\n endedAt: window.endedAt,\n messageIds: window.messages.map((message) => message.id),\n };\n }\n\n async refreshWindowForMessage(input: {\n messageId: string;\n windowMs: number;\n summarize: (window: EpisodeWindow) => Promise<string>;\n }): Promise<EpisodeSummaryRecord | undefined> {\n const target = this.database\n .prepare(\n `\n SELECT chat_id AS chatId, sent_at AS sentAt\n FROM messages\n WHERE id = ?\n `,\n )\n .get(input.messageId) as { chatId: string; sentAt: string } | undefined;\n\n if (!target) {\n return undefined;\n }\n\n const existingWindow = this.database\n .prepare(\n `\n SELECT e.started_at AS startedAt, e.ended_at AS endedAt\n FROM messages target\n JOIN messages source\n ON source.id = json_extract(target.raw_payload_json, '$.derivedFromMessageId')\n JOIN memory_episode_messages mem ON mem.message_id = source.id\n JOIN memory_episodes e ON e.id = mem.episode_id\n WHERE target.id = ?\n LIMIT 1\n `,\n )\n .get(input.messageId) as { startedAt: string; endedAt: string } | undefined;\n if (!existingWindow) {\n return undefined;\n }\n\n const messageTime = toMillis(target.sentAt);\n const windowStart = toMillis(existingWindow.startedAt);\n const windowEnd = Math.max(toMillis(existingWindow.endedAt), messageTime);\n\n const rows = this.database\n .prepare(\n `\n SELECT\n m.id,\n m.chat_id AS chatId,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.text,\n m.sent_at AS sentAt\n FROM messages m\n JOIN chats c ON c.id = m.chat_id\n WHERE m.chat_id = ?\n ORDER BY m.sent_at ASC\n `,\n )\n .all(target.chatId) as EpisodeMessage[];\n\n const windowMessages = rows.filter((message) => {\n const time = toMillis(message.sentAt);\n return time >= windowStart && time <= windowEnd;\n });\n const first = windowMessages[0];\n const last = windowMessages.at(-1);\n if (!first || !last) {\n return undefined;\n }\n\n const window: EpisodeWindow = {\n chatId: first.chatId,\n chatName: first.chatName,\n startedAt: first.sentAt,\n endedAt: last.sentAt,\n messages: windowMessages,\n };\n const summary = await input.summarize(window);\n return this.insertEpisode(window, summary);\n }\n\n getEpisodeCount(): number {\n const row = this.database.prepare(\"SELECT count(*) AS count FROM memory_episodes\").get() as { count: number };\n return row.count;\n }\n\n listRecentEpisodes(limit = 20): EpisodeListItem[] {\n return this.database\n .prepare(\n `\n SELECT\n e.id,\n e.chat_id AS chatId,\n c.name AS chatName,\n e.summary,\n e.message_count AS messageCount,\n e.started_at AS startedAt,\n e.ended_at AS endedAt,\n e.created_at AS createdAt\n FROM memory_episodes e\n JOIN chats c ON c.id = e.chat_id\n ORDER BY e.ended_at DESC\n LIMIT ?\n `,\n )\n .all(limit) as EpisodeListItem[];\n }\n\n searchEpisodes(query: string, limit = 8, scope?: MessageSearchScope): EpisodeSearchResult[] {\n const ftsQuery = escapeFtsQuery(query);\n const scopeWhere = buildScopeWhere(scope);\n return this.database\n .prepare(\n `\n SELECT\n e.id AS chunkId,\n e.id AS messageId,\n 'episode' AS platform,\n e.summary AS text,\n 1.0 AS score,\n 'episode' AS messageType,\n c.name AS chatName,\n '会话记忆' AS senderName,\n e.ended_at AS sentAt,\n e.started_at AS startedAt,\n e.ended_at AS endedAt,\n (\n SELECT json_group_array(message_id)\n FROM (\n SELECT message_id\n FROM memory_episode_messages\n WHERE episode_id = e.id\n ORDER BY position ASC\n )\n ) AS sourceMessageIdsJson\n FROM memory_episodes_fts fts\n JOIN memory_episodes e ON e.id = fts.episode_id\n JOIN chats c ON c.id = e.chat_id\n WHERE memory_episodes_fts MATCH ?\n ${scopeWhere.where}\n GROUP BY e.id\n ORDER BY e.ended_at DESC\n LIMIT ?\n `,\n )\n .all(ftsQuery, ...scopeWhere.params, limit)\n .map((row) => {\n const item = row as MessageSearchResult & {\n startedAt: string;\n endedAt: string;\n sourceMessageIdsJson: string;\n };\n return {\n ...item,\n sourceMessageIds: JSON.parse(item.sourceMessageIdsJson) as string[],\n };\n });\n }\n}\n","const SECRET_PATTERNS: Array<[RegExp, string]> = [\n [/-----BEGIN [^-]+ PRIVATE KEY-----[\\s\\S]*?-----END [^-]+ PRIVATE KEY-----/g, \"[REDACTED_SECRET]\"],\n [/(\\bAuthorization\\s*:\\s*Bearer\\s+)[A-Za-z0-9._~+/=-]{12,}/gi, \"$1[REDACTED_SECRET]\"],\n [/(https?:\\/\\/)[^\\s/@:]+:[^\\s/@]+@/gi, \"$1[REDACTED_SECRET]@\"],\n [/([?&](?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|session(?:id)?|client[_-]?secret)=)[^\\s&,。;;]+/gi, \"$1[REDACTED_SECRET]\"],\n [/(\"(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|session(?:id)?|client[_-]?secret|private[_-]?key)\"\\s*:\\s*\")[^\"]+(\")/gi, \"$1[REDACTED_SECRET]$2\"],\n [/(\\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|session(?:id)?|client[_-]?secret)\\s*[=:]\\s*)[^\\s;,。]+/gi, \"$1[REDACTED_SECRET]\"],\n [/\\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\\b/g, \"[REDACTED_SECRET]\"],\n [/\\bxox[baprs]-[A-Za-z0-9-]{20,}\\b/g, \"[REDACTED_SECRET]\"],\n [/\\bsk-[A-Za-z0-9_-]{6,}\\b/g, \"[REDACTED_SECRET]\"],\n];\n\nexport function sanitizeEpisodeSummary(summary: string): string {\n let sanitized = summary;\n for (const [pattern, replacement] of SECRET_PATTERNS) {\n sanitized = sanitized.replace(pattern, replacement);\n }\n return sanitized;\n}\n","import { EpisodeRepository } from \"../episodes/repository.js\";\nimport type { EpisodeSearchResult } from \"../episodes/repository.js\";\nimport type { EvidenceBlock } from \"./types.js\";\nimport type { Retriever, RetrievalScope } from \"./retriever.js\";\n\nfunction toEpisodeEvidence(result: EpisodeSearchResult): EvidenceBlock {\n return {\n id: result.chunkId,\n text: result.text,\n score: result.score,\n source: {\n type: \"episode\",\n label: result.chatName,\n sender: result.senderName,\n timestamp: result.endedAt,\n location: `${result.startedAt} - ${result.endedAt}`,\n },\n };\n}\n\nexport class EpisodeFtsRetriever implements Retriever {\n constructor(private readonly episodes: EpisodeRepository) {}\n\n async retrieve(question: string, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n return this.episodes.searchEpisodes(question, 8, scope).map(toEpisodeEvidence);\n }\n}\n","import type { Retriever, RetrievalScope } from \"./retriever.js\";\nimport type { EvidenceBlock } from \"./types.js\";\n\nexport interface HybridRetrieverOptions {\n limit?: number;\n scope?: RetrievalScope;\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\nfunction evidenceTimestampMs(evidence: EvidenceBlock): number {\n const timestamp = evidence.source.timestamp;\n if (!timestamp) {\n return 0;\n }\n\n const parsed = Date.parse(timestamp);\n return Number.isFinite(parsed) ? parsed : 0;\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, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n const effectiveScope = scope ?? this.options.scope;\n const results = await Promise.all(this.retrievers.map((retriever) => retriever.retrieve(question, effectiveScope)));\n const merged = new Map<string, EvidenceBlock>();\n\n for (const evidenceList of results) {\n for (const evidence of evidenceList) {\n const existing = merged.get(evidence.id);\n const score = normalizeScore(evidence.score);\n\n if (!existing || score > existing.score) {\n merged.set(evidence.id, {\n ...evidence,\n score,\n });\n }\n }\n }\n\n return [...merged.values()]\n .sort((left, right) => right.score - left.score || evidenceTimestampMs(right) - evidenceTimestampMs(left))\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, RetrievalScope } 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, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n const results = this.messages.searchMessages(question, 8, {\n excludeMessageIds: this.options.excludeMessageIds,\n scope,\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 { Retriever, RetrievalScope } from \"./retriever.js\";\nimport type { ChatTool, EvidenceBlock } from \"./types.js\";\n\nexport interface RagSearchTool extends ChatTool {\n execute(input: unknown): Promise<EvidenceBlock[]>;\n}\n\nexport interface CreateRagSearchToolsInput {\n hybrid: Retriever;\n messages: Retriever;\n episodes: Retriever;\n semantic?: Retriever;\n scope?: RetrievalScope;\n}\n\nconst searchInputSchema = {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Search query written by the model.\" },\n limit: { type: \"number\", description: \"Maximum number of evidence blocks to return.\" },\n },\n required: [\"query\"],\n additionalProperties: false,\n};\n\ninterface SearchInput {\n query: string;\n limit: number;\n}\n\nfunction parseSearchInput(input: unknown): SearchInput {\n const rawQuery =\n typeof input === \"object\" && input !== null && \"query\" in input\n ? (input as { query?: unknown }).query\n : undefined;\n\n if (typeof rawQuery !== \"string\") {\n throw new Error(\"搜索 query 必须是非空字符串。\");\n }\n\n const query = rawQuery.trim();\n if (!query) {\n throw new Error(\"搜索 query 必须是非空字符串。\");\n }\n\n const rawLimit =\n typeof input === \"object\" && input !== null && \"limit\" in input\n ? (input as { limit?: unknown }).limit\n : undefined;\n const numericLimit = typeof rawLimit === \"number\" && Number.isFinite(rawLimit) ? rawLimit : 5;\n const limit = Math.min(12, Math.max(1, Math.floor(numericLimit)));\n\n return { query, limit };\n}\n\nasync function runRetriever(retriever: Retriever, input: unknown, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n const { query, limit } = parseSearchInput(input);\n const results = await retriever.retrieve(query, scope);\n return results.slice(0, limit);\n}\n\nfunction createSearchTool(name: string, description: string, retriever: Retriever, scope?: RetrievalScope): RagSearchTool {\n return {\n name,\n description,\n inputSchema: searchInputSchema,\n execute: (input) => runRetriever(retriever, input, scope),\n };\n}\n\nexport async function executeRagSearchTool(tool: RagSearchTool, input: unknown): Promise<EvidenceBlock[]> {\n const { limit } = parseSearchInput(input);\n const results = await tool.execute(input);\n return results.slice(0, limit);\n}\n\nexport function createRagSearchTools(input: CreateRagSearchToolsInput): RagSearchTool[] {\n const tools: RagSearchTool[] = [\n createSearchTool(\n \"hybrid_search\",\n \"Search across all indexed RAG evidence using the default hybrid retrieval strategy.\",\n input.hybrid,\n input.scope,\n ),\n createSearchTool(\n \"search_messages\",\n \"Search chat messages only when the answer likely depends on message-level evidence.\",\n input.messages,\n input.scope,\n ),\n createSearchTool(\n \"search_episodes\",\n \"Search episode summaries only when the answer likely depends on longer-running context.\",\n input.episodes,\n input.scope,\n ),\n ];\n\n if (input.semantic) {\n tools.push(\n createSearchTool(\n \"semantic_search\",\n \"Search semantic vector evidence only when broader conceptual recall is needed.\",\n input.semantic,\n input.scope,\n ),\n );\n }\n\n return tools;\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 type { MessageSearchScope } from \"../messages/types.js\";\nimport { cosineSimilarity } from \"./embedding.js\";\nimport type { EvidenceSource } from \"./types.js\";\nimport type { VectorRecord, VectorSearchResult, VectorStore } from \"./vector-store.js\";\n\ninterface SearchRow {\n chunkId: string;\n text: string;\n chatName: string;\n senderName: string;\n sentAt: string;\n embeddingJson: string;\n}\n\nfunction parseEmbeddingJson(value: string): number[] {\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) && parsed.every((item) => typeof item === \"number\") ? parsed : [];\n } catch {\n return [];\n }\n}\n\nfunction toEvidenceSource(row: SearchRow): EvidenceSource {\n return {\n type: \"message\",\n label: row.chatName,\n sender: row.senderName,\n timestamp: row.sentAt,\n };\n}\n\nfunction buildScopeWhere(scope?: MessageSearchScope): { where: string; params: string[] } {\n const clauses: string[] = [];\n const params: string[] = [];\n if (scope?.platform) {\n clauses.push(\"m.platform = ?\");\n params.push(scope.platform);\n }\n if (scope?.platformChatId) {\n clauses.push(\"c.platform_chat_id = ?\");\n params.push(scope.platformChatId);\n }\n\n return {\n where: clauses.length > 0 ? `AND ${clauses.join(\" AND \")}` : \"\",\n params,\n };\n}\n\nexport class SqliteVectorStore implements VectorStore {\n constructor(\n private readonly database: SqliteDatabase,\n private readonly options: { model: string },\n ) {}\n\n async upsert(records: VectorRecord[]): Promise<void> {\n if (records.length === 0) {\n return;\n }\n\n const updatedAt = new Date().toISOString();\n const statement = this.database.prepare(`\n INSERT INTO message_chunk_embeddings (chunk_id, model, dimension, embedding_json, updated_at)\n VALUES (@chunkId, @model, @dimension, @embeddingJson, @updatedAt)\n ON CONFLICT(chunk_id, model)\n DO UPDATE SET\n dimension = excluded.dimension,\n embedding_json = excluded.embedding_json,\n updated_at = excluded.updated_at\n `);\n\n const transaction = this.database.transaction((input: VectorRecord[]) => {\n for (const record of input) {\n statement.run({\n chunkId: record.id,\n model: this.options.model,\n dimension: record.vector.length,\n embeddingJson: JSON.stringify(record.vector),\n updatedAt,\n });\n }\n });\n\n transaction(records);\n }\n\n async search(vector: number[], limit: number, scope?: MessageSearchScope): Promise<VectorSearchResult[]> {\n if (limit <= 0) {\n return [];\n }\n\n const scopeWhere = buildScopeWhere(scope);\n const rows = this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n mc.text AS text,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt,\n e.embedding_json AS embeddingJson\n FROM message_chunk_embeddings e\n JOIN message_chunks mc ON mc.id = e.chunk_id\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n WHERE e.model = ?\n ${scopeWhere.where}\n `,\n )\n .all(this.options.model, ...scopeWhere.params) as SearchRow[];\n\n return rows\n .flatMap((row) => {\n const storedVector = parseEmbeddingJson(row.embeddingJson);\n if (storedVector.length === 0) {\n return [];\n }\n\n const vectorScore = cosineSimilarity(vector, storedVector);\n return {\n id: row.chunkId,\n text: row.text,\n score: vectorScore,\n vectorScore,\n source: toEvidenceSource(row),\n };\n })\n .sort((left, right) => right.vectorScore - left.vectorScore)\n .slice(0, limit);\n }\n\n count(): number {\n const row = this.database\n .prepare(\"SELECT COUNT(*) AS count FROM message_chunk_embeddings WHERE model = ?\")\n .get(this.options.model) as { count: number };\n\n return row.count;\n }\n}\n","import type { EmbeddingModel } from \"./embedding.js\";\nimport type { Retriever, RetrievalScope } 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, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n const vector = await this.embedding.embed(question);\n return this.store.search(vector, this.limit, scope);\n }\n}\n\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { EpisodeRepository } from \"../episodes/repository.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport type { MessageSearchScope } from \"../messages/types.js\";\nimport { createEmbeddingModel } from \"../llm/openai-compatible.js\";\nimport { EpisodeFtsRetriever } from \"./episode-retriever.js\";\nimport { HybridRetriever } from \"./hybrid-retriever.js\";\nimport { MessageFtsRetriever } from \"./message-retriever.js\";\nimport type { Retriever } from \"./retriever.js\";\nimport { createRagSearchTools, type RagSearchTool } from \"./search-tools.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 database: SqliteDatabase;\n messages: MessageRepository;\n excludeMessageIds?: string[];\n scope?: MessageSearchScope;\n}): Promise<{ retriever: Retriever; close: () => void }> {\n const retrievers: Retriever[] = [\n new EpisodeFtsRetriever(new EpisodeRepository(input.database)),\n new MessageFtsRetriever(input.messages, { excludeMessageIds: input.excludeMessageIds }),\n ];\n const closers: Array<() => void> = [];\n\n if (hasEmbeddingConfig(input.config, input.secrets)) {\n const vectorStore = new SqliteVectorStore(input.database, {\n model: input.config.embedding.model,\n });\n retrievers.push(new VectorRetriever(createEmbeddingModel(input.config, input.secrets), vectorStore));\n }\n\n return {\n retriever: new HybridRetriever(retrievers, { scope: input.scope }),\n close: () => {\n for (const closer of closers) {\n closer();\n }\n },\n };\n}\n\nexport async function createAgenticRagSearchTools(input: {\n config: AppConfig;\n secrets: AppSecrets;\n database: SqliteDatabase;\n messages: MessageRepository;\n excludeMessageIds?: string[];\n scope?: MessageSearchScope;\n}): Promise<{ tools: RagSearchTool[]; close: () => void }> {\n const episodes = new EpisodeFtsRetriever(new EpisodeRepository(input.database));\n const messages = new MessageFtsRetriever(input.messages, { excludeMessageIds: input.excludeMessageIds });\n const semantic = hasEmbeddingConfig(input.config, input.secrets)\n ? new VectorRetriever(\n createEmbeddingModel(input.config, input.secrets),\n new SqliteVectorStore(input.database, { model: input.config.embedding.model }),\n )\n : undefined;\n const hybrid = new HybridRetriever(semantic ? [episodes, messages, semantic] : [episodes, messages]);\n\n return {\n tools: createRagSearchTools({ hybrid, messages, episodes, semantic, scope: input.scope }),\n close: () => {},\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 type { ChatModel } from \"../rag/types.js\";\nimport type { EpisodeWindow } from \"./repository.js\";\nimport { sanitizeEpisodeSummary } from \"./sanitizer.js\";\n\nexport async function summarizeEpisodeWindow(window: EpisodeWindow, model: ChatModel, now: Date): Promise<string> {\n const transcript = window.messages\n .map((message) => `[${message.sentAt}] ${message.senderName}:${message.text}`)\n .join(\"\\n\");\n\n const summary = await model.complete([\n {\n role: \"system\",\n content:\n \"你是 ChatterCatcher 的会话记忆整理模块。你的任务是把碎片化闲聊整理成可检索事实,补全短消息、代词、缩写与上下文之间的关系。只总结明确事实,不要编造。保留重要数字、日期、链接和代码;如果内容像密码、API key、token 或密钥,只描述其上下文关系,不要在摘要中复写原文。消息里的“今天”“明天”“昨晚”“下周三”等相对时间表述,请基于每条消息前的发送时间戳推导为具体日期写入摘要。例如 [2026-05-05T20:00:00.000Z] 妈妈说“明天要用丝丝露”,摘要应写为“2026-05-06 要用丝丝露”。\",\n },\n {\n role: \"user\",\n content: `当前时间:${now.toISOString()}\\n群聊:${window.chatName}\\n时间:${window.startedAt} - ${window.endedAt}\\n\\n聊天记录:\\n${transcript}\\n\\n请输出一段简洁的会话记忆摘要。`,\n },\n ]);\n\n return sanitizeEpisodeSummary(summary);\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport type { ChatModel } from \"../rag/types.js\";\nimport { EpisodeRepository } from \"./repository.js\";\nimport { summarizeEpisodeWindow } from \"./summarizer.js\";\n\nexport interface ProcessEpisodesResult {\n created: number;\n}\n\nexport async function processEpisodesNow(input: {\n config: AppConfig;\n secrets: AppSecrets;\n database: SqliteDatabase;\n model: ChatModel;\n now?: Date;\n}): Promise<ProcessEpisodesResult> {\n const episodes = new EpisodeRepository(input.database);\n const created = await episodes.summarizeReadyWindows({\n now: input.now ?? new Date(),\n quietMs: input.config.episodes.quietMinutes * 60 * 1000,\n windowMs: input.config.episodes.windowMinutes * 60 * 1000,\n summarize: (window, now) => summarizeEpisodeWindow(window, input.model, now),\n });\n\n return { created: created.length };\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\n\nexport interface ResolveFeishuBotOpenIdOptions {\n fetch?: typeof fetch;\n}\n\nexport interface EnsureFeishuBotOpenIdOptions extends ResolveFeishuBotOpenIdOptions {\n onSave?: () => Promise<void>;\n}\n\nfunction getOpenApiBaseUrl(domain: AppConfig[\"feishu\"][\"domain\"]): string {\n return domain === \"lark\" ? \"https://open.larksuite.com/open-apis\" : \"https://open.feishu.cn/open-apis\";\n}\n\nasync function readFeishuJson(response: Response): Promise<unknown> {\n if (!response.ok) {\n throw new Error(`飞书接口请求失败:HTTP ${response.status}`);\n }\n\n return response.json();\n}\n\nfunction assertFeishuSuccess(payload: unknown, fallbackMessage: string): asserts payload is Record<string, unknown> {\n if (!payload || typeof payload !== \"object\") {\n throw new Error(fallbackMessage);\n }\n\n const code = (payload as { code?: unknown }).code;\n if (code !== 0) {\n const message = (payload as { msg?: unknown }).msg;\n throw new Error(typeof message === \"string\" ? message : fallbackMessage);\n }\n}\n\nexport async function resolveFeishuBotOpenId(\n config: AppConfig,\n secrets: AppSecrets,\n options: ResolveFeishuBotOpenIdOptions = {},\n): Promise<string> {\n if (!config.feishu.appId || !secrets.feishu.appSecret) {\n throw new Error(\"飞书 App ID 或 App Secret 未配置。\");\n }\n\n const fetchImpl = options.fetch ?? fetch;\n const baseUrl = getOpenApiBaseUrl(config.feishu.domain);\n const tokenPayload = await readFeishuJson(\n await fetchImpl(`${baseUrl}/auth/v3/tenant_access_token/internal`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n app_id: config.feishu.appId,\n app_secret: secrets.feishu.appSecret,\n }),\n }),\n );\n assertFeishuSuccess(tokenPayload, \"获取飞书 tenant_access_token 失败。\");\n\n const tenantAccessToken = tokenPayload.tenant_access_token;\n if (typeof tenantAccessToken !== \"string\" || !tenantAccessToken) {\n throw new Error(\"飞书 tenant_access_token 响应缺少 token。\");\n }\n\n const botInfoPayload = await readFeishuJson(\n await fetchImpl(`${baseUrl}/bot/v3/info`, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${tenantAccessToken}` },\n }),\n );\n assertFeishuSuccess(botInfoPayload, \"获取飞书机器人信息失败。\");\n\n const bot = botInfoPayload.bot;\n if (!bot || typeof bot !== \"object\") {\n throw new Error(\"飞书机器人信息响应缺少 bot。\");\n }\n\n const openId = (bot as { open_id?: unknown }).open_id;\n if (typeof openId !== \"string\" || !openId) {\n throw new Error(\"飞书机器人信息响应缺少 open_id。\");\n }\n\n return openId;\n}\n\nexport async function ensureFeishuBotOpenId(\n config: AppConfig,\n secrets: AppSecrets,\n options: EnsureFeishuBotOpenIdOptions = {},\n): Promise<string> {\n if (config.feishu.botOpenId) {\n return config.feishu.botOpenId;\n }\n\n const openId = await resolveFeishuBotOpenId(config, secrets, options);\n const previousOpenId = config.feishu.botOpenId;\n config.feishu.botOpenId = openId;\n try {\n await options.onSave?.();\n } catch (error) {\n config.feishu.botOpenId = previousOpenId;\n throw error;\n }\n return openId;\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport { CronJobRepository } from \"../cron/jobs.js\";\nimport { generateCronJobMessage } from \"../cron/generator.js\";\nimport type { CronJobScheduler } from \"../cron/scheduler.js\";\nimport { createCronJobScheduler } from \"../cron/scheduler.js\";\nimport { processEpisodesNow } from \"../episodes/manual-process.js\";\nimport type { GatewayIngestAndDownloadResult, GatewayIngestor } from \"../gateway/ingest.js\";\nimport type { IndexingScheduler } from \"../gateway/indexing-scheduler.js\";\nimport { createIndexingScheduler } from \"../gateway/indexing-scheduler.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { createAgenticRagSearchTools } from \"../rag/factory.js\";\nimport { processMessagesNow } from \"../rag/manual-index.js\";\nimport type { ChatModel } from \"../rag/types.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport type { FeishuReceiveMessageEvent } from \"./normalize.js\";\nimport { ImageMultimodalTaskRepository } from \"../multimodal/tasks.js\";\nimport type { MultimodalModel } from \"../multimodal/types.js\";\nimport { ImageMultimodalWorker } from \"../multimodal/worker.js\";\nimport { getFeishuQuestionDecision, isFeishuMessageAddressedToBot } from \"./question.js\";\nimport type { FeishuQuestionHandler } from \"./question.js\";\nimport { FeishuResourceDownloader } from \"./resource-downloader.js\";\nimport type { MessageSender } from \"./sender.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 episodeProcessor?: { database: SqliteDatabase; model: ChatModel; now?: () => Date };\n imageMultimodalProcessor?: { database: SqliteDatabase; model: MultimodalModel };\n indexingProcessor?: { database: SqliteDatabase };\n indexingScheduler?: IndexingScheduler;\n cronJobProcessor?: { database: SqliteDatabase; model: ChatModel; sender: Pick<MessageSender, \"sendTextToChat\"> };\n cronJobScheduler?: CronJobScheduler;\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\nfunction formatGatewayStartError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"PingInterval\") || message.includes(\"system busy\") || message.includes(\"1000040345\")) {\n return new Error(`飞书长连接启动失败,请检查 App ID / App Secret 是否正确;原始错误:${message}`);\n }\n\n return error instanceof Error ? error : new Error(message);\n}\n\nexport function createFeishuEventDispatcher(options: {\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 episodeProcessor?: { database: SqliteDatabase; model: ChatModel; now?: () => Date };\n imageMultimodalProcessor?: { database: SqliteDatabase; model: MultimodalModel };\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, options.config)) {\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 secrets: options.secrets,\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 (options.episodeProcessor) {\n const episodeResult = await processEpisodesNow({\n config: options.config,\n secrets: options.secrets,\n database: options.episodeProcessor.database,\n model: options.episodeProcessor.model,\n now: options.episodeProcessor.now?.(),\n });\n if (episodeResult.created > 0) {\n console.log(`飞书会话记忆已生成:${episodeResult.created}`);\n }\n }\n\n if (result.attachment?.downloaded) {\n console.log(`飞书附件已下载:${result.attachment.downloaded.storedPath}`);\n if (options.imageMultimodalProcessor && result.attachment.imageTask) {\n void new ImageMultimodalWorker({\n config: options.config,\n messages: new MessageRepository(options.imageMultimodalProcessor.database),\n tasks: new ImageMultimodalTaskRepository(options.imageMultimodalProcessor.database),\n model: options.imageMultimodalProcessor.model,\n multimodalModelName: options.config.multimodal.model,\n vectorIndexMessage: options.attachmentVectorIndexer,\n }).processPending().then((imageResult) => {\n console.log(\n `飞书图片多模态处理完成:processed=${imageResult.processed}, succeeded=${imageResult.succeeded}, skipped=${imageResult.skipped}, failed=${imageResult.failed}`,\n );\n }).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`飞书图片多模态处理失败:${message}`);\n });\n }\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 secrets: options.secrets,\n ingestor: options.ingestor,\n questionHandler: options.questionHandler,\n resourceDownloader: options.resourceDownloader,\n attachmentVectorIndexer: options.attachmentVectorIndexer,\n episodeProcessor: options.episodeProcessor,\n imageMultimodalProcessor: options.imageMultimodalProcessor,\n });\n\n const indexingScheduler = options.indexingScheduler ?? (\n options.indexingProcessor\n ? createIndexingScheduler({\n schedule: options.config.schedules.indexing,\n work: async () => {\n await processMessagesNow({\n config: options.config,\n secrets: options.secrets,\n database: options.indexingProcessor!.database,\n limit: 10_000,\n });\n },\n })\n : undefined\n );\n\n const cronJobScheduler = options.cronJobScheduler ?? (\n options.cronJobProcessor\n ? createCronJobScheduler({\n repository: new CronJobRepository(options.cronJobProcessor.database),\n sendTextToChat: (chatId, text) => options.cronJobProcessor!.sender.sendTextToChat(chatId, text),\n generateMessage: async (job, now) => {\n const { tools, close } = await createAgenticRagSearchTools({\n config: options.config,\n secrets: options.secrets,\n database: options.cronJobProcessor!.database,\n messages: new MessageRepository(options.cronJobProcessor!.database),\n scope: { platform: \"feishu\", platformChatId: job.chatId },\n });\n try {\n return await generateCronJobMessage({ prompt: job.prompt, model: options.cronJobProcessor!.model, tools, now });\n } finally {\n close();\n }\n },\n })\n : undefined\n );\n\n return {\n async start() {\n try {\n await wsClient.start({ eventDispatcher });\n indexingScheduler?.start();\n cronJobScheduler?.start();\n } catch (error) {\n indexingScheduler?.stop();\n cronJobScheduler?.stop();\n throw formatGatewayStartError(error);\n }\n },\n stop() {\n indexingScheduler?.stop();\n cronJobScheduler?.stop();\n wsClient.close({ force: true });\n },\n };\n}\n","import crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { getNextCronRun, isValidCronSchedule } from \"./schedule.js\";\n\nexport type CronJobStatus = \"active\" | \"deleted\";\n\nexport interface CronJobRecord {\n id: string;\n chatId: string;\n createdByOpenId?: string;\n schedule: string;\n prompt: string;\n status: CronJobStatus;\n lastRunAt?: string;\n nextRunAt: string;\n lastError?: string;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface CronJobRepositoryOptions {\n now?: () => Date;\n}\n\ninterface CronJobRow {\n id: string;\n chatId: string;\n createdByOpenId: string | null;\n schedule: string;\n prompt: string;\n status: CronJobStatus;\n lastRunAt: string | null;\n nextRunAt: string;\n lastError: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport class CronJobRepository {\n private readonly now: () => Date;\n\n constructor(\n private readonly database: SqliteDatabase,\n options: CronJobRepositoryOptions = {},\n ) {\n this.now = options.now ?? (() => new Date());\n }\n\n create(input: {\n chatId: string;\n createdByOpenId?: string;\n schedule: string;\n prompt: string;\n }): CronJobRecord {\n const schedule = input.schedule.trim();\n const prompt = input.prompt.trim();\n if (!isValidCronSchedule(schedule)) {\n throw new Error(\"cron 表达式无效。\");\n }\n if (!prompt) {\n throw new Error(\"定时任务 prompt 不能为空。\");\n }\n\n const now = this.now();\n const nextRunAt = getNextCronRun(schedule, now);\n if (!nextRunAt) {\n throw new Error(\"无法计算下一次执行时间。\");\n }\n\n const record: CronJobRecord = {\n id: crypto.randomUUID(),\n chatId: input.chatId,\n createdByOpenId: input.createdByOpenId,\n schedule,\n prompt,\n status: \"active\",\n nextRunAt: nextRunAt.toISOString(),\n createdAt: now.toISOString(),\n updatedAt: now.toISOString(),\n };\n\n this.database\n .prepare(\n `\n INSERT INTO cron_jobs (\n id, chat_id, created_by_open_id, schedule, prompt, status,\n last_run_at, next_run_at, last_error, created_at, updated_at\n )\n VALUES (\n @id, @chatId, @createdByOpenId, @schedule, @prompt, @status,\n NULL, @nextRunAt, NULL, @createdAt, @updatedAt\n )\n `,\n )\n .run(record);\n\n return record;\n }\n\n get(id: string): CronJobRecord | null {\n return this.listByWhere(\"WHERE id = ?\", [id], 1)[0] ?? null;\n }\n\n list(limit = 100): CronJobRecord[] {\n return this.listByWhere(\"\", [], limit);\n }\n\n listByChat(chatId: string, limit = 50): CronJobRecord[] {\n return this.listByWhere(\n \"WHERE chat_id = ? AND status = 'active'\",\n [chatId],\n limit,\n );\n }\n\n listDue(now: Date, limit = 20): CronJobRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n chat_id AS chatId,\n created_by_open_id AS createdByOpenId,\n schedule,\n prompt,\n status,\n last_run_at AS lastRunAt,\n next_run_at AS nextRunAt,\n last_error AS lastError,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM cron_jobs\n WHERE status = 'active' AND next_run_at <= ?\n ORDER BY next_run_at ASC, updated_at ASC\n LIMIT ?\n `,\n )\n .all(now.toISOString(), limit) as CronJobRow[];\n\n return rows.map((row) => ({\n id: row.id,\n chatId: row.chatId,\n createdByOpenId: row.createdByOpenId ?? undefined,\n schedule: row.schedule,\n prompt: row.prompt,\n status: row.status,\n lastRunAt: row.lastRunAt ?? undefined,\n nextRunAt: row.nextRunAt,\n lastError: row.lastError ?? undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n }));\n }\n\n deleteByChat(id: string, chatId: string): boolean {\n const now = this.now().toISOString();\n const result = this.database\n .prepare(\n `\n UPDATE cron_jobs\n SET status = 'deleted', updated_at = @updatedAt\n WHERE id = @id AND chat_id = @chatId AND status = 'active'\n `,\n )\n .run({ id, chatId, updatedAt: now });\n return result.changes > 0;\n }\n\n markSuccess(id: string, ranAt: Date): void {\n const job = this.get(id);\n if (!job) {\n return;\n }\n\n const nextRunAt = getNextCronRun(job.schedule, ranAt);\n if (!nextRunAt) {\n throw new Error(\"无法计算下一次执行时间。\");\n }\n\n this.database\n .prepare(\n `\n UPDATE cron_jobs\n SET last_run_at = @lastRunAt, next_run_at = @nextRunAt, last_error = NULL, updated_at = @updatedAt\n WHERE id = @id AND status = 'active'\n `,\n )\n .run({\n id,\n lastRunAt: ranAt.toISOString(),\n nextRunAt: nextRunAt.toISOString(),\n updatedAt: ranAt.toISOString(),\n });\n }\n\n markFailure(id: string, error: string, failedAt: Date): void {\n const job = this.get(id);\n if (!job) {\n return;\n }\n\n const nextRunAt = getNextCronRun(job.schedule, failedAt);\n if (!nextRunAt) {\n throw new Error(\"无法计算下一次执行时间。\");\n }\n\n this.database\n .prepare(\n `\n UPDATE cron_jobs\n SET last_run_at = @lastRunAt, last_error = @lastError, next_run_at = @nextRunAt, updated_at = @updatedAt\n WHERE id = @id AND status = 'active'\n `,\n )\n .run({\n id,\n lastRunAt: failedAt.toISOString(),\n lastError: error,\n nextRunAt: nextRunAt.toISOString(),\n updatedAt: failedAt.toISOString(),\n });\n }\n\n private listByWhere(\n whereSql: string,\n params: unknown[],\n limit: number,\n ): CronJobRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n chat_id AS chatId,\n created_by_open_id AS createdByOpenId,\n schedule,\n prompt,\n status,\n last_run_at AS lastRunAt,\n next_run_at AS nextRunAt,\n last_error AS lastError,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM cron_jobs\n ${whereSql}\n ORDER BY updated_at DESC\n LIMIT ?\n `,\n )\n .all(...params, limit) as CronJobRow[];\n\n return rows.map((row) => ({\n id: row.id,\n chatId: row.chatId,\n createdByOpenId: row.createdByOpenId ?? undefined,\n schedule: row.schedule,\n prompt: row.prompt,\n status: row.status,\n lastRunAt: row.lastRunAt ?? undefined,\n nextRunAt: row.nextRunAt,\n lastError: row.lastError ?? undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n }));\n }\n}\n","interface ParsedCronSchedule {\n minute: ParsedField;\n hour: ParsedField;\n dayOfMonth: ParsedField;\n month: ParsedField;\n dayOfWeek: ParsedField;\n}\n\ntype FieldMatcher = (value: number) => boolean;\n\ninterface ParsedField {\n wildcard: boolean;\n matches: FieldMatcher;\n}\n\nexport function isValidCronSchedule(schedule: string): boolean {\n return parseCronSchedule(schedule) !== null;\n}\n\nexport function matchesCronSchedule(schedule: string, date: Date): boolean {\n const parsed = parseCronSchedule(schedule);\n if (!parsed) {\n return false;\n }\n\n return matchesParsedSchedule(parsed, date);\n}\n\nexport function getNextCronRun(schedule: string, after: Date): Date | null {\n const parsed = parseCronSchedule(schedule);\n if (!parsed) {\n return null;\n }\n\n const candidate = new Date(after);\n candidate.setSeconds(0, 0);\n candidate.setMinutes(candidate.getMinutes() + 1);\n\n const maxMinutes = 5 * 366 * 24 * 60;\n for (let i = 0; i < maxMinutes; i += 1) {\n if (matchesParsedSchedule(parsed, candidate)) {\n return new Date(candidate);\n }\n candidate.setMinutes(candidate.getMinutes() + 1);\n }\n\n return null;\n}\n\nfunction matchesParsedSchedule(schedule: ParsedCronSchedule, date: Date): boolean {\n const dayOfMonthMatches = schedule.dayOfMonth.matches(date.getDate());\n const dayOfWeekMatches = schedule.dayOfWeek.matches(date.getDay());\n const dayMatches = schedule.dayOfMonth.wildcard || schedule.dayOfWeek.wildcard\n ? dayOfMonthMatches && dayOfWeekMatches\n : dayOfMonthMatches || dayOfWeekMatches;\n\n return (\n schedule.minute.matches(date.getMinutes()) &&\n schedule.hour.matches(date.getHours()) &&\n dayMatches &&\n schedule.month.matches(date.getMonth() + 1)\n );\n}\n\nfunction parseCronSchedule(schedule: string): ParsedCronSchedule | null {\n const fields = schedule.trim().split(/\\s+/);\n if (fields.length !== 5) {\n return null;\n }\n\n const minute = parseMinuteField(fields[0]);\n const hour = parseExactOrWildcardField(fields[1], 0, 23);\n const dayOfMonth = parseExactOrWildcardField(fields[2], 1, 31);\n const month = parseExactOrWildcardField(fields[3], 1, 12);\n const dayOfWeek = parseExactOrWildcardField(fields[4], 0, 6);\n\n if (!minute || !hour || !dayOfMonth || !month || !dayOfWeek) {\n return null;\n }\n\n return { minute, hour, dayOfMonth, month, dayOfWeek };\n}\n\nfunction parseMinuteField(field: string): ParsedField | null {\n if (field === \"*\") {\n return { wildcard: true, matches: () => true };\n }\n\n const stepMatch = /^\\*\\/(\\d+)$/.exec(field);\n if (stepMatch) {\n const step = Number(stepMatch[1]);\n if (!Number.isInteger(step) || step <= 0 || step > 59) {\n return null;\n }\n\n return { wildcard: false, matches: (value) => value % step === 0 };\n }\n\n if (field.includes(\",\")) {\n const values = field.split(\",\").map((part) => parseExactNumber(part, 0, 59));\n if (values.some((value) => value === null)) {\n return null;\n }\n\n const allowed = new Set(values as number[]);\n return { wildcard: false, matches: (value) => allowed.has(value) };\n }\n\n const exact = parseExactNumber(field, 0, 59);\n if (exact === null) {\n return null;\n }\n\n return { wildcard: false, matches: (value) => value === exact };\n}\n\nfunction parseExactOrWildcardField(field: string, min: number, max: number): ParsedField | null {\n if (field === \"*\") {\n return { wildcard: true, matches: () => true };\n }\n\n const exact = parseExactNumber(field, min, max);\n if (exact === null) {\n return null;\n }\n\n return { wildcard: false, matches: (value) => value === exact };\n}\n\nfunction parseExactNumber(field: string, min: number, max: number): number | null {\n if (!/^\\d+$/.test(field)) {\n return null;\n }\n\n const value = Number(field);\n if (!Number.isInteger(value) || value < min || value > max) {\n return null;\n }\n\n return value;\n}\n","import type { RagSearchTool } from \"../rag/search-tools.js\";\nimport type { ChatMessage, ChatModel, EvidenceBlock } from \"../rag/types.js\";\n\ninterface GenerateCronJobMessageInput {\n prompt: string;\n model: ChatModel;\n tools: RagSearchTool[];\n now: Date;\n maxModelTurns?: number;\n maxToolCalls?: number;\n}\n\nconst SYSTEM_PROMPT =\n \"你正在为飞书群生成一条定时消息。可以先调用搜索工具检索本地群聊知识库。最终输出必须是可以直接发到群里的纯文本,不要输出工具调用说明。\";\n\nfunction evidenceToText(evidence: EvidenceBlock[]): string {\n if (evidence.length === 0) {\n return \"无检索证据。\";\n }\n\n return evidence.map((item, index) => `${index + 1}. ${item.text}`).join(\"\\n\");\n}\n\nfunction toolResultContent(results: EvidenceBlock[]): string {\n return JSON.stringify(results.map((item) => ({ id: item.id, text: item.text, score: item.score, source: item.source })));\n}\n\nexport async function generateCronJobMessage(input: GenerateCronJobMessageInput): Promise<string> {\n if (!input.model.completeWithTools) {\n throw new Error(\"当前 LLM 客户端不支持工具调用。\");\n }\n\n const messages: ChatMessage[] = [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: `当前时间:${input.now.toISOString()}\\n任务提示词:${input.prompt}` },\n ];\n const toolsByName = new Map(input.tools.map((tool) => [tool.name, tool]));\n const evidence: EvidenceBlock[] = [];\n const maxModelTurns = input.maxModelTurns ?? 3;\n const maxToolCalls = input.maxToolCalls ?? 6;\n let toolCallsUsed = 0;\n\n for (let turn = 0; turn < maxModelTurns; turn += 1) {\n const result = await input.model.completeWithTools(messages, input.tools);\n messages.push({ role: \"assistant\", content: result.content, toolCalls: result.toolCalls, reasoningContent: result.reasoningContent });\n\n if (result.toolCalls.length === 0) {\n break;\n }\n\n for (const call of result.toolCalls) {\n if (toolCallsUsed >= maxToolCalls) {\n return input.model.complete([\n { role: \"system\", content: SYSTEM_PROMPT },\n {\n role: \"user\",\n content: `当前时间:${input.now.toISOString()}\\n任务提示词:${input.prompt}\\n\\n证据:\\n${evidenceToText(evidence)}`,\n },\n ]);\n }\n\n toolCallsUsed += 1;\n const tool = toolsByName.get(call.name);\n if (!tool) {\n messages.push({ role: \"tool\", toolCallId: call.id, content: JSON.stringify({ error: `未知工具:${call.name}` }) });\n continue;\n }\n\n try {\n const results = await tool.execute(call.input);\n evidence.push(...results);\n messages.push({ role: \"tool\", toolCallId: call.id, content: toolResultContent(results) });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n messages.push({ role: \"tool\", toolCallId: call.id, content: JSON.stringify({ error: message }) });\n }\n }\n }\n\n return input.model.complete([\n { role: \"system\", content: SYSTEM_PROMPT },\n {\n role: \"user\",\n content: `当前时间:${input.now.toISOString()}\\n任务提示词:${input.prompt}\\n\\n证据:\\n${evidenceToText(evidence)}`,\n },\n ]);\n}\n","import type { CronJobRecord, CronJobRepository } from \"./jobs.js\";\n\nexport interface CronJobScheduler {\n start(): void;\n stop(): void;\n runDueNow(): Promise<void>;\n}\n\ninterface CreateCronJobSchedulerOptions {\n repository: Pick<CronJobRepository, \"listDue\" | \"markSuccess\" | \"markFailure\">;\n generateMessage: (job: CronJobRecord, now: Date) => Promise<string>;\n sendTextToChat: (chatId: string, text: string) => Promise<void>;\n now?: () => Date;\n setIntervalFn?: typeof setInterval;\n clearIntervalFn?: typeof clearInterval;\n logger?: Pick<Console, \"error\">;\n}\n\nexport function createCronJobScheduler(options: CreateCronJobSchedulerOptions): CronJobScheduler {\n const now = options.now ?? (() => new Date());\n const setIntervalFn = options.setIntervalFn ?? setInterval;\n const clearIntervalFn = options.clearIntervalFn ?? clearInterval;\n const logger = options.logger ?? console;\n let timer: ReturnType<typeof setInterval> | undefined;\n let running = false;\n\n const runDueNow = async (): Promise<void> => {\n if (running) {\n return;\n }\n\n running = true;\n const startedAt = now();\n try {\n const jobs = options.repository.listDue(startedAt);\n for (const job of jobs) {\n try {\n const text = await options.generateMessage(job, startedAt);\n await options.sendTextToChat(job.chatId, text);\n options.repository.markSuccess(job.id, startedAt);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n options.repository.markFailure(job.id, message, startedAt);\n logger.error(`CRONJob 执行失败:${job.id} ${message}`);\n }\n }\n } finally {\n running = false;\n }\n };\n\n return {\n start() {\n if (timer) {\n return;\n }\n\n void runDueNow();\n timer = setIntervalFn(() => {\n void runDueNow();\n }, 60_000);\n },\n stop() {\n if (!timer) {\n return;\n }\n\n clearIntervalFn(timer);\n timer = undefined;\n },\n runDueNow,\n };\n}\n","export interface IndexingScheduler {\n start(): void;\n stop(): void;\n runDueNow(): Promise<void>;\n}\n\ninterface CreateIndexingSchedulerOptions {\n schedule: string;\n work: () => Promise<void>;\n now?: () => Date;\n setIntervalFn?: typeof setInterval;\n clearIntervalFn?: typeof clearInterval;\n logger?: Pick<Console, \"error\" | \"warn\">;\n}\n\ninterface ParsedCronSchedule {\n minute: MinuteMatcher;\n hour: FieldMatcher;\n dayOfMonth: FieldMatcher;\n month: FieldMatcher;\n dayOfWeek: FieldMatcher;\n}\n\ntype FieldMatcher = (value: number) => boolean;\ntype MinuteMatcher = FieldMatcher;\n\nexport function matchesCronMinuteSchedule(schedule: string, date: Date): boolean {\n const parsed = parseCronSchedule(schedule);\n if (!parsed) {\n return false;\n }\n\n return (\n parsed.minute(date.getMinutes()) &&\n parsed.hour(date.getHours()) &&\n parsed.dayOfMonth(date.getDate()) &&\n parsed.month(date.getMonth() + 1) &&\n parsed.dayOfWeek(date.getDay())\n );\n}\n\nexport function createIndexingScheduler(options: CreateIndexingSchedulerOptions): IndexingScheduler {\n const now = options.now ?? (() => new Date());\n const setIntervalFn = options.setIntervalFn ?? setInterval;\n const clearIntervalFn = options.clearIntervalFn ?? clearInterval;\n const logger = options.logger ?? console;\n const parsed = parseCronSchedule(options.schedule);\n\n let timer: ReturnType<typeof setInterval> | undefined;\n let running = false;\n\n const runDueNow = async (): Promise<void> => {\n if (!parsed || running || !matchesParsedSchedule(parsed, now())) {\n return;\n }\n\n running = true;\n try {\n await options.work();\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(`定时消息索引失败:${message}`);\n } finally {\n running = false;\n }\n };\n\n return {\n start() {\n if (!parsed || timer) {\n return;\n }\n\n timer = setIntervalFn(() => {\n void runDueNow();\n }, 60_000);\n },\n stop() {\n if (!timer) {\n return;\n }\n\n clearIntervalFn(timer);\n timer = undefined;\n },\n runDueNow,\n };\n}\n\nfunction matchesParsedSchedule(schedule: ParsedCronSchedule, date: Date): boolean {\n return (\n schedule.minute(date.getMinutes()) &&\n schedule.hour(date.getHours()) &&\n schedule.dayOfMonth(date.getDate()) &&\n schedule.month(date.getMonth() + 1) &&\n schedule.dayOfWeek(date.getDay())\n );\n}\n\nfunction parseCronSchedule(schedule: string): ParsedCronSchedule | null {\n const fields = schedule.trim().split(/\\s+/);\n if (fields.length !== 5) {\n return null;\n }\n\n const minute = parseMinuteField(fields[0]);\n const hour = parseExactOrWildcardField(fields[1], 0, 23);\n const dayOfMonth = parseExactOrWildcardField(fields[2], 1, 31);\n const month = parseExactOrWildcardField(fields[3], 1, 12);\n const dayOfWeek = parseExactOrWildcardField(fields[4], 0, 6);\n\n if (!minute || !hour || !dayOfMonth || !month || !dayOfWeek) {\n return null;\n }\n\n return { minute, hour, dayOfMonth, month, dayOfWeek };\n}\n\nfunction parseMinuteField(field: string): MinuteMatcher | null {\n if (field === \"*\") {\n return () => true;\n }\n\n const stepMatch = /^\\*\\/(\\d+)$/.exec(field);\n if (stepMatch) {\n const step = Number(stepMatch[1]);\n if (!Number.isInteger(step) || step <= 0 || step > 59) {\n return null;\n }\n\n return (value) => value % step === 0;\n }\n\n if (field.includes(\",\")) {\n const values = field.split(\",\").map((part) => parseExactNumber(part, 0, 59));\n if (values.some((value) => value === null)) {\n return null;\n }\n\n const allowed = new Set(values as number[]);\n return (value) => allowed.has(value);\n }\n\n const exact = parseExactNumber(field, 0, 59);\n if (exact === null) {\n return null;\n }\n\n return (value) => value === exact;\n}\n\nfunction parseExactOrWildcardField(field: string, min: number, max: number): FieldMatcher | null {\n if (field === \"*\") {\n return () => true;\n }\n\n const exact = parseExactNumber(field, min, max);\n if (exact === null) {\n return null;\n }\n\n return (value) => value === exact;\n}\n\nfunction parseExactNumber(field: string, min: number, max: number): number | null {\n if (!/^\\d+$/.test(field)) {\n return null;\n }\n\n const value = Number(field);\n if (!Number.isInteger(value) || value < min || value > max) {\n return null;\n }\n\n return value;\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 type { EmbeddingModel } from \"./embedding.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 embedding?: EmbeddingModel;\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 model: input.config.embedding.model,\n });\n const embedding = input.embedding ?? createEmbeddingModel(input.config, input.secrets);\n const stats = await indexMessageChunks({\n messages: new MessageRepository(input.database),\n embedding,\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}\n","import crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport type {\n EnqueueImageMultimodalTaskInput,\n ImageMultimodalTaskRecord,\n ImageMultimodalTaskStatus,\n} from \"./types.js\";\n\ninterface ImageMultimodalTaskRow {\n id: string;\n source_message_id: string;\n platform_message_id: string;\n image_key: string;\n stored_path: string;\n mime_type: string;\n status: ImageMultimodalTaskStatus;\n attempts: number;\n last_error: string | null;\n derived_message_id: string | null;\n created_at: string;\n updated_at: string;\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction stableId(sourceMessageId: string, imageKey: string): string {\n return crypto.createHash(\"sha256\").update(`${sourceMessageId}\u001f${imageKey}`).digest(\"hex\").slice(0, 32);\n}\n\nfunction mapRow(row: ImageMultimodalTaskRow | undefined): ImageMultimodalTaskRecord | undefined {\n if (!row) {\n return undefined;\n }\n\n return {\n id: row.id,\n sourceMessageId: row.source_message_id,\n platformMessageId: row.platform_message_id,\n imageKey: row.image_key,\n storedPath: row.stored_path,\n mimeType: row.mime_type,\n status: row.status,\n attempts: row.attempts,\n ...(row.last_error ? { lastError: row.last_error } : {}),\n ...(row.derived_message_id ? { derivedMessageId: row.derived_message_id } : {}),\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport class ImageMultimodalTaskRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n enqueue(input: EnqueueImageMultimodalTaskInput): ImageMultimodalTaskRecord {\n const id = stableId(input.sourceMessageId, input.imageKey);\n const timestamp = nowIso();\n\n this.database\n .prepare(\n `\n INSERT INTO image_multimodal_tasks (\n id,\n source_message_id,\n platform_message_id,\n image_key,\n stored_path,\n mime_type,\n status,\n attempts,\n created_at,\n updated_at\n )\n VALUES (\n @id,\n @sourceMessageId,\n @platformMessageId,\n @imageKey,\n @storedPath,\n @mimeType,\n 'pending',\n 0,\n @createdAt,\n @updatedAt\n )\n ON CONFLICT(source_message_id, image_key)\n DO UPDATE SET\n platform_message_id = excluded.platform_message_id,\n stored_path = excluded.stored_path,\n mime_type = excluded.mime_type,\n status = 'pending',\n attempts = 0,\n last_error = NULL,\n derived_message_id = NULL,\n updated_at = excluded.updated_at\n `,\n )\n .run({\n id,\n sourceMessageId: input.sourceMessageId,\n platformMessageId: input.platformMessageId,\n imageKey: input.imageKey,\n storedPath: input.storedPath,\n mimeType: input.mimeType,\n createdAt: timestamp,\n updatedAt: timestamp,\n });\n\n const record = this.getById(id);\n if (!record) {\n throw new Error(`图片多模态任务写入失败:${id}`);\n }\n\n return record;\n }\n\n listPending(limit = 10): ImageMultimodalTaskRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n source_message_id,\n platform_message_id,\n image_key,\n stored_path,\n mime_type,\n status,\n attempts,\n last_error,\n derived_message_id,\n created_at,\n updated_at\n FROM image_multimodal_tasks\n WHERE status = 'pending'\n ORDER BY updated_at ASC\n LIMIT ?\n `,\n )\n .all(limit) as ImageMultimodalTaskRow[];\n\n return rows.map((row) => mapRow(row)).filter((row): row is ImageMultimodalTaskRecord => Boolean(row));\n }\n\n markRunning(id: string): ImageMultimodalTaskRecord {\n const result = this.database\n .prepare(\n `\n UPDATE image_multimodal_tasks\n SET status = 'running',\n attempts = attempts + 1,\n last_error = NULL,\n updated_at = @updatedAt\n WHERE id = @id AND status = 'pending'\n `,\n )\n .run({ id, updatedAt: nowIso() });\n\n if (result.changes === 0) {\n throw new Error(`图片多模态任务状态无法更新:${id}`);\n }\n\n return this.requireById(id);\n }\n\n markSucceeded(id: string, derivedMessageId: string): ImageMultimodalTaskRecord {\n this.database\n .prepare(\n `\n UPDATE image_multimodal_tasks\n SET status = 'succeeded',\n last_error = NULL,\n derived_message_id = @derivedMessageId,\n updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({ id, derivedMessageId, updatedAt: nowIso() });\n\n return this.requireById(id);\n }\n\n markSkipped(id: string, reason: string): ImageMultimodalTaskRecord {\n this.database\n .prepare(\n `\n UPDATE image_multimodal_tasks\n SET status = 'skipped',\n last_error = @reason,\n derived_message_id = NULL,\n updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({ id, reason, updatedAt: nowIso() });\n\n return this.requireById(id);\n }\n\n markFailed(id: string, error: string, finalFailure: boolean): ImageMultimodalTaskRecord {\n this.database\n .prepare(\n `\n UPDATE image_multimodal_tasks\n SET status = @status,\n last_error = @error,\n derived_message_id = NULL,\n updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({ id, status: finalFailure ? \"failed\" : \"pending\", error, updatedAt: nowIso() });\n\n return this.requireById(id);\n }\n\n getById(id: string): ImageMultimodalTaskRecord | undefined {\n const row = this.database\n .prepare(\n `\n SELECT\n id,\n source_message_id,\n platform_message_id,\n image_key,\n stored_path,\n mime_type,\n status,\n attempts,\n last_error,\n derived_message_id,\n created_at,\n updated_at\n FROM image_multimodal_tasks\n WHERE id = ?\n `,\n )\n .get(id) as ImageMultimodalTaskRow | undefined;\n\n return mapRow(row);\n }\n\n private requireById(id: string): ImageMultimodalTaskRecord {\n const record = this.getById(id);\n if (!record) {\n throw new Error(`图片多模态任务不存在:${id}`);\n }\n return record;\n }\n}\n","import type { AppConfig } from \"../config/schema.js\";\nimport type { EpisodeRepository, EpisodeWindow } from \"../episodes/repository.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport type { ImageMultimodalTaskRepository } from \"./tasks.js\";\nimport type { ImageMultimodalTaskRecord, MultimodalModel } from \"./types.js\";\n\nexport interface ImageMultimodalWorkerResult {\n processed: number;\n succeeded: number;\n skipped: number;\n failed: number;\n}\n\nexport interface ImageMultimodalWorkerOptions {\n config: AppConfig;\n messages: MessageRepository;\n tasks: ImageMultimodalTaskRepository;\n model: MultimodalModel;\n multimodalModelName: string;\n episodes?: EpisodeRepository;\n vectorIndexMessage?: (messageId: string) => Promise<{ chunks: number; vectors: number }>;\n summarizeEpisode?: (window: EpisodeWindow) => Promise<string>;\n}\n\nexport class ImageMultimodalWorker {\n constructor(private readonly options: ImageMultimodalWorkerOptions) {}\n\n async processPending(limit = 10): Promise<ImageMultimodalWorkerResult> {\n const result: ImageMultimodalWorkerResult = { processed: 0, succeeded: 0, skipped: 0, failed: 0 };\n const pending = this.options.tasks.listPending(limit);\n\n for (const task of pending) {\n result.processed += 1;\n await this.processTask(task, result);\n }\n\n return result;\n }\n\n private async processTask(task: ImageMultimodalTaskRecord, result: ImageMultimodalWorkerResult): Promise<void> {\n let running: ImageMultimodalTaskRecord;\n try {\n running = this.options.tasks.markRunning(task.id);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.startsWith(\"图片多模态任务状态无法更新:\")) {\n return;\n }\n throw error;\n }\n\n try {\n const described = await this.options.model.describeImage({\n imagePath: running.storedPath,\n mimeType: running.mimeType,\n });\n\n if (!described.isMeaningful) {\n this.options.tasks.markSkipped(running.id, described.reason || \"多模态模型判定图片无意义。\");\n result.skipped += 1;\n return;\n }\n\n const derivedMessageId = this.options.messages.createImageSummaryMessage({\n sourceMessageId: running.sourceMessageId,\n imageKey: running.imageKey,\n summary: described.summary,\n reason: described.reason,\n multimodalModel: this.options.multimodalModelName,\n generatedAt: new Date().toISOString(),\n });\n\n if (this.options.vectorIndexMessage) {\n await this.options.vectorIndexMessage(derivedMessageId);\n }\n if (this.options.episodes && this.options.summarizeEpisode) {\n await this.options.episodes.refreshWindowForMessage({\n messageId: derivedMessageId,\n windowMs: this.options.config.episodes.windowMinutes * 60 * 1000,\n summarize: this.options.summarizeEpisode,\n });\n }\n\n this.options.tasks.markSucceeded(running.id, derivedMessageId);\n result.succeeded += 1;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.options.tasks.markFailed(running.id, message, running.attempts >= 3);\n result.failed += 1;\n }\n }\n}\n","import type { ChatTool } from \"../rag/types.js\";\nimport type { CronJobRepository } from \"./jobs.js\";\n\nexport interface CronJobTool extends ChatTool {\n execute(input: unknown): Promise<string>;\n}\n\ninterface CreateCronJobToolsInput {\n repository: CronJobRepository;\n chatId: string;\n createdByOpenId?: string;\n}\n\nfunction readString(input: unknown, key: string): string {\n const value =\n typeof input === \"object\" && input !== null && key in input\n ? (input as Record<string, unknown>)[key]\n : undefined;\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${key} 必须是非空字符串。`);\n }\n return value.trim();\n}\n\nexport function createCronJobTools(input: CreateCronJobToolsInput): CronJobTool[] {\n return [\n {\n name: \"create_cron_job\",\n description:\n \"Create a scheduled AI message for the current Feishu chat only. The schedule must be a five-field cron string.\",\n inputSchema: {\n type: \"object\",\n properties: {\n schedule: {\n type: \"string\",\n description: \"Five-field cron schedule, for example 0 9 * * *.\",\n },\n prompt: {\n type: \"string\",\n description: \"Prompt used later to generate the scheduled message.\",\n },\n },\n required: [\"schedule\", \"prompt\"],\n additionalProperties: false,\n },\n execute: async (rawInput) => {\n const job = input.repository.create({\n chatId: input.chatId,\n createdByOpenId: input.createdByOpenId,\n schedule: readString(rawInput, \"schedule\"),\n prompt: readString(rawInput, \"prompt\"),\n });\n return JSON.stringify({ ok: true, job });\n },\n },\n {\n name: \"list_cron_jobs\",\n description: \"List active scheduled AI messages for the current Feishu chat only.\",\n inputSchema: { type: \"object\", properties: {}, additionalProperties: false },\n execute: async () => JSON.stringify({ ok: true, jobs: input.repository.listByChat(input.chatId) }),\n },\n {\n name: \"delete_cron_job\",\n description: \"Delete a scheduled AI message by ID, only if it belongs to the current Feishu chat.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"Cron job ID returned by create_cron_job or list_cron_jobs.\",\n },\n },\n required: [\"id\"],\n additionalProperties: false,\n },\n execute: async (rawInput) => {\n const id = readString(rawInput, \"id\");\n const ok = input.repository.deleteByChat(id, input.chatId);\n return JSON.stringify({\n ok,\n id,\n message: ok ? \"定时任务已删除。\" : \"没有找到当前群里的这个定时任务。\",\n });\n },\n },\n ];\n}\n","import crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport interface QaLogRecord {\n id: string;\n chatId: string | null;\n questionMessageId: string | null;\n question: string;\n answer: string;\n citations: unknown[];\n retrievalDebug: Record<string, unknown>;\n status: \"answered\" | \"failed\";\n error: string | null;\n createdAt: string;\n}\n\nexport interface CreateQaLogInput {\n chatId?: string;\n questionMessageId?: string;\n question: string;\n answer: string;\n citations: unknown[];\n retrievalDebug: Record<string, unknown>;\n status: \"answered\" | \"failed\";\n error?: string;\n createdAt: string;\n}\n\ninterface QaLogRow {\n id: string;\n chat_id: string | null;\n question_message_id: string | null;\n question: string;\n answer: string;\n citations_json: string;\n retrieval_debug_json: string;\n status: \"answered\" | \"failed\";\n error: string | null;\n created_at: string;\n}\n\nfunction clampLimit(limit: number): number {\n return Math.max(1, Math.min(200, Math.trunc(limit)));\n}\n\nexport class QaLogRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n create(input: CreateQaLogInput): QaLogRecord {\n const record: QaLogRecord = {\n id: `qa_${crypto.randomUUID()}`,\n chatId: input.chatId ?? null,\n questionMessageId: input.questionMessageId ?? null,\n question: input.question,\n answer: input.answer,\n citations: input.citations,\n retrievalDebug: input.retrievalDebug,\n status: input.status,\n error: input.error ?? null,\n createdAt: input.createdAt,\n };\n\n this.database\n .prepare(\n `\n INSERT INTO qa_logs (\n id,\n chat_id,\n question_message_id,\n question,\n answer,\n citations_json,\n retrieval_debug_json,\n status,\n error,\n created_at\n )\n VALUES (\n @id,\n @chatId,\n @questionMessageId,\n @question,\n @answer,\n @citationsJson,\n @retrievalDebugJson,\n @status,\n @error,\n @createdAt\n )\n `,\n )\n .run({\n id: record.id,\n chatId: record.chatId,\n questionMessageId: record.questionMessageId,\n question: record.question,\n answer: record.answer,\n citationsJson: JSON.stringify(record.citations),\n retrievalDebugJson: JSON.stringify(record.retrievalDebug),\n status: record.status,\n error: record.error,\n createdAt: record.createdAt,\n });\n\n return record;\n }\n\n listRecent(limit: number): QaLogRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n chat_id,\n question_message_id,\n question,\n answer,\n citations_json,\n retrieval_debug_json,\n status,\n error,\n created_at\n FROM qa_logs\n ORDER BY created_at DESC\n LIMIT ?\n `,\n )\n .all(clampLimit(limit)) as QaLogRow[];\n\n return rows.map((row) => ({\n id: row.id,\n chatId: row.chat_id,\n questionMessageId: row.question_message_id,\n question: row.question,\n answer: row.answer,\n citations: JSON.parse(row.citations_json) as unknown[],\n retrievalDebug: JSON.parse(row.retrieval_debug_json) as Record<string, unknown>,\n status: row.status,\n error: row.error,\n createdAt: row.created_at,\n }));\n }\n\n getCount(): number {\n const row = this.database.prepare(\"SELECT COUNT(*) AS count FROM qa_logs\").get() as { count: number };\n return row.count;\n }\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport { CronJobRepository } from \"../cron/jobs.js\";\nimport type { CronJobTool } from \"../cron/tools.js\";\nimport { createCronJobTools } from \"../cron/tools.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { createAgenticRagSearchTools } from \"../rag/factory.js\";\nimport { QaLogRepository } from \"../rag/qa-logs.js\";\nimport type { RagSearchTool } from \"../rag/search-tools.js\";\nimport type { ChatMessage, ChatModel, ChatTool, EvidenceBlock } 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(/@/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\ntype FeishuExecutableTool = (RagSearchTool | CronJobTool) & ChatTool;\n\nconst FEISHU_TOOL_SYSTEM_PROMPT =\n `你是飞书群聊助手。你可以先搜索本地知识来回答问题;当用户明确要求创建、查看或删除群消息定时任务时,也可以调用定时任务工具。定时任务工具只管理当前群聊,不能跨群操作。若用户用自然语言描述时间,你需要先将其转换为五字段 cron 表达式(分 时 日 月 周),再调用工具。当前时间会提供给你。检索证据中的时间戳是消息被发送时的真实时间。回答时若涉及相对时间表述(如消息中说”明天””今晚”),必须基于证据中每条消息的时间戳推导为具体日期,不要照搬原文的相对表述。对于一般问答,先按需调用搜索工具,再基于工具返回的证据直接给出最终答案;若引用了检索结果,要在答案里直接写出引用内容。不要声称完成了未实际调用的操作。重要:你的回答必须是面向群成员的自然语言,绝对不能输出 JSON、工具调用细节或原始的搜索结果格式。用户只应看到你整合后的最终答案。`;\n\nconst DEFAULT_MAX_MODEL_TURNS = 4;\nconst DEFAULT_MAX_TOOL_CALLS = 8;\nconst FEISHU_TOOL_LOOP_FALLBACK = \"定时任务操作已提交,但模型没有生成最终回复。\";\nconst FEISHU_TOOL_LOOP_LIMIT_REACHED = \"工具调用次数已达到上限,请缩小请求后重试。\";\n\nfunction toToolResultContent(value: unknown): string {\n if (typeof value === \"string\") return value;\n return JSON.stringify(value);\n}\n\nfunction isEvidenceBlockArray(value: unknown): value is EvidenceBlock[] {\n return Array.isArray(value) && value.length > 0 && typeof (value[0] as EvidenceBlock)?.text === \"string\";\n}\n\nfunction formatEvidenceBlocks(blocks: EvidenceBlock[]): string {\n return blocks\n .map((block, index) => {\n const source = block.source;\n const sender = source.sender ? `${source.sender} ` : \"\";\n const timestamp = source.timestamp ? `(${source.timestamp.slice(0, 19).replace(\"T\", \" \")})` : \"\";\n const header = `[证据${index + 1}] ${sender}${timestamp}:`;\n return `${header}\\n${block.text}`;\n })\n .join(\"\\n\\n\");\n}\n\nfunction toToolErrorContent(message: string): string {\n return JSON.stringify({ ok: false, error: message });\n}\n\nasync function executeFeishuTool(tool: FeishuExecutableTool, input: unknown): Promise<string> {\n const result = await tool.execute(input);\n if (isEvidenceBlockArray(result)) {\n return formatEvidenceBlocks(result);\n }\n return toToolResultContent(result);\n}\n\nasync function runFeishuToolLoop(input: {\n question: string;\n now: Date;\n model: ChatModel;\n tools: FeishuExecutableTool[];\n maxModelTurns?: number;\n maxToolCalls?: number;\n}): Promise<string> {\n if (!input.model.completeWithTools) {\n throw new Error(\"当前 LLM 客户端不支持工具调用。\");\n }\n\n const maxModelTurns = input.maxModelTurns ?? DEFAULT_MAX_MODEL_TURNS;\n const maxToolCalls = input.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;\n const messages: ChatMessage[] = [\n { role: \"system\", content: FEISHU_TOOL_SYSTEM_PROMPT },\n { role: \"user\", content: `当前时间:${input.now.toISOString()}\\n问题:${input.question}` },\n ];\n const toolsByName = new Map(input.tools.map((tool) => [tool.name, tool]));\n let toolCallsUsed = 0;\n\n for (let turn = 0; turn < maxModelTurns; turn += 1) {\n const assistantResult = await input.model.completeWithTools(messages, input.tools);\n messages.push({\n role: \"assistant\",\n content: assistantResult.content,\n toolCalls: assistantResult.toolCalls,\n reasoningContent: assistantResult.reasoningContent,\n });\n\n if (assistantResult.toolCalls.length === 0) {\n return assistantResult.content || FEISHU_TOOL_LOOP_FALLBACK;\n }\n\n for (const toolCall of assistantResult.toolCalls) {\n if (toolCallsUsed >= maxToolCalls) {\n return FEISHU_TOOL_LOOP_LIMIT_REACHED;\n }\n\n toolCallsUsed += 1;\n const tool = toolsByName.get(toolCall.name);\n\n if (!tool) {\n messages.push({\n role: \"tool\",\n toolCallId: toolCall.id,\n content: toToolErrorContent(`未知工具:${toolCall.name}`),\n });\n continue;\n }\n\n try {\n const result = await executeFeishuTool(tool, toolCall.input);\n messages.push({\n role: \"tool\",\n toolCallId: toolCall.id,\n content: result,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n messages.push({\n role: \"tool\",\n toolCallId: toolCall.id,\n content: toToolErrorContent(message),\n });\n }\n }\n }\n\n // Salvage: try one final completion without tools to generate an answer\n try {\n const salvageAnswer = await input.model.complete([\n ...messages,\n { role: \"system\", content: \"请基于以上所有工具返回的信息,直接给出最终答案。不要再调用工具。\" },\n ]);\n return salvageAnswer || \"抱歉,回答生成失败,请稍后重试。\";\n } catch {\n return \"抱歉,回答生成失败,请稍后重试。\";\n }\n}\n\ntype FeishuMessage = NonNullable<NonNullable<FeishuReceiveMessageEvent[\"event\"]>[\"message\"]>;\ntype FeishuMention = NonNullable<FeishuMessage[\"mentions\"]>[number];\n\nfunction isMentionForBot(mention: FeishuMention, config: AppConfig): boolean {\n if (!config.feishu.botOpenId) {\n return false;\n }\n\n return mention.id?.open_id === config.feishu.botOpenId;\n}\n\nfunction getBotMentions(payload: FeishuReceiveMessageEvent, config: AppConfig) {\n const message = payload.event?.message;\n return (message?.mentions ?? []).filter((mention) => isMentionForBot(mention, config));\n}\n\nexport function isFeishuMessageAddressedToBot(payload: FeishuReceiveMessageEvent, config: AppConfig): boolean {\n const message = payload.event?.message;\n if (!message || message.message_type !== \"text\") {\n return false;\n }\n\n return getBotMentions(payload, config).length > 0;\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 = getBotMentions(payload, config);\n const text = parseTextContent(message.content);\n const hasMention = isFeishuMessageAddressedToBot(payload, config);\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 const now = new Date();\n const qaLogs = new QaLogRepository(this.options.database);\n await this.acknowledgeQuestion(decision.chatId, questionMessageId);\n\n const { tools, close } = await createAgenticRagSearchTools({\n config: this.options.config,\n secrets: this.options.secrets,\n database: this.options.database,\n messages: new MessageRepository(this.options.database),\n excludeMessageIds: options.excludeMessageIds,\n });\n\n try {\n try {\n const cronTools = createCronJobTools({\n repository: new CronJobRepository(this.options.database),\n chatId: decision.chatId,\n createdByOpenId: payload.event?.sender?.sender_id?.open_id,\n });\n const allTools: FeishuExecutableTool[] = [...tools, ...cronTools];\n const answer = await runFeishuToolLoop({\n question: decision.question,\n now,\n tools: allTools,\n model: this.options.model,\n });\n qaLogs.create({\n chatId: decision.chatId,\n questionMessageId,\n question: decision.question,\n answer,\n citations: [],\n retrievalDebug: {},\n status: \"answered\",\n createdAt: new Date().toISOString(),\n });\n await this.sendResponse(decision.chatId, questionMessageId, answer);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n qaLogs.create({\n chatId: decision.chatId,\n questionMessageId,\n question: decision.question,\n answer: `暂时无法回答:${message}`,\n citations: [],\n retrievalDebug: {},\n status: \"failed\",\n error: message,\n createdAt: new Date().toISOString(),\n });\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 { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport 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\";\nimport { ImageMultimodalTaskRepository } from \"../multimodal/tasks.js\";\nimport type { ImageMultimodalTaskRecord } from \"../multimodal/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 imageTask?: ImageMultimodalTaskRecord;\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\nfunction isMultimodalReady(config: AppConfig, secrets: AppSecrets): boolean {\n return Boolean(config.multimodal.baseUrl && config.multimodal.model && secrets.multimodal.apiKey);\n}\n\nexport class GatewayIngestor {\n private readonly messages: MessageRepository;\n private readonly jobs: FileJobRepository;\n private readonly imageTasks: ImageMultimodalTaskRepository;\n\n constructor(database: SqliteDatabase) {\n this.messages = new MessageRepository(database);\n this.jobs = new FileJobRepository(database);\n this.imageTasks = new ImageMultimodalTaskRepository(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: AppConfig;\n secrets: AppSecrets;\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 (attachment.kind === \"image\") {\n const imageTask = isMultimodalReady(input.config, input.secrets)\n ? this.imageTasks.enqueue({\n sourceMessageId: result.messageId,\n platformMessageId: result.message.platformMessageId,\n imageKey: attachment.fileKey,\n storedPath: downloaded.storedPath,\n mimeType: attachment.mimeType || \"image/jpeg\",\n })\n : undefined;\n\n return {\n ...result,\n attachment: {\n downloaded,\n ...(imageTask ? { imageTask } : {}),\n skippedReason: imageTask ? \"图片已下载,等待多模态后台处理。\" : \"图片已下载,但多模态未配置。\",\n },\n };\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, type ChildProcess } 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\nconst START_FAILURE_GRACE_MS = 250;\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\nfunction describeImmediateChildFailure(event: { type: \"error\"; error: Error } | { type: \"exit\"; code: number | null; signal: NodeJS.Signals | null }): string {\n if (event.type === \"error\") {\n return event.error.message;\n }\n\n return event.signal ? `signal=${event.signal}` : `exitCode=${event.code ?? \"unknown\"}`;\n}\n\nfunction waitForImmediateChildFailure(\n child: ChildProcess,\n graceMs = START_FAILURE_GRACE_MS,\n): Promise<{ type: \"error\"; error: Error } | { type: \"exit\"; code: number | null; signal: NodeJS.Signals | null } | null> {\n return new Promise((resolve) => {\n let settled = false;\n let timer: NodeJS.Timeout;\n\n const cleanup = () => {\n clearTimeout(timer);\n child.off(\"error\", onError);\n child.off(\"exit\", onExit);\n };\n const settle = (result: { type: \"error\"; error: Error } | { type: \"exit\"; code: number | null; signal: NodeJS.Signals | null } | null) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(result);\n };\n const onError = (error: Error) => settle({ type: \"error\", error });\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => settle({ type: \"exit\", code, signal });\n\n child.once(\"error\", onError);\n child.once(\"exit\", onExit);\n timer = setTimeout(() => settle(null), graceMs);\n });\n}\n\nexport async function startDetachedGateway(input: DetachedGatewayStartInput): Promise<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 const immediateFailure = await waitForImmediateChildFailure(child);\n closeStdio();\n\n if (immediateFailure) {\n return {\n started: false,\n message: `飞书 Gateway 启动失败:${describeImmediateChildFailure(immediateFailure)}。请查看日志:${logFile}`,\n pid: child.pid,\n logFile,\n };\n }\n\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 fs from \"node:fs/promises\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { DescribeImageInput, DescribeImageResult, MultimodalModel } from \"./types.js\";\n\nexport interface OpenAICompatibleMultimodalOptions {\n baseUrl: string;\n apiKey: string;\n model: string;\n temperature?: number;\n}\n\ninterface MultimodalCompletionResponse {\n choices?: Array<{\n message?: {\n content?: string;\n };\n }>;\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\");\n}\n\nfunction buildPrompt(context?: string): string {\n const contextText = context?.trim();\n return [\n \"请理解这张图片,判断它是否包含值得进入知识库和会话记忆的有意义信息。\",\n \"请只输出 JSON,格式为 {\\\"summary\\\": string, \\\"isMeaningful\\\": boolean, \\\"reason\\\": string}。\",\n \"summary 使用简洁中文转述图片中的关键信息;无意义图片也要给出简短 summary。\",\n contextText ? `上下文:${contextText}` : undefined,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nfunction parseDescribeImageResult(content: string): DescribeImageResult {\n let data: unknown;\n try {\n data = JSON.parse(content);\n } catch {\n throw new Error(\"多模态模型返回的 JSON 无法解析。\");\n }\n\n if (!data || typeof data !== \"object\") {\n throw new Error(\"多模态模型返回格式不正确。\");\n }\n\n const result = data as Record<string, unknown>;\n const summary = typeof result.summary === \"string\" ? result.summary.trim() : \"\";\n if (!summary) {\n throw new Error(\"多模态模型返回的 summary 为空。\");\n }\n if (typeof result.isMeaningful !== \"boolean\") {\n throw new Error(\"多模态模型返回的 isMeaningful 不是布尔值。\");\n }\n\n const reason = typeof result.reason === \"string\" ? result.reason.trim() : \"\";\n return {\n summary,\n isMeaningful: result.isMeaningful,\n ...(reason ? { reason } : {}),\n };\n}\n\nexport class OpenAICompatibleMultimodalModel implements MultimodalModel {\n constructor(private readonly options: OpenAICompatibleMultimodalOptions) {}\n\n async describeImage(input: DescribeImageInput): Promise<DescribeImageResult> {\n if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {\n throw new Error(\"多模态配置不完整。请运行 chattercatcher setup 或 chattercatcher settings。\");\n }\n\n const image = await fs.readFile(input.imagePath);\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 {\n role: \"user\",\n content: [\n { type: \"text\", text: buildPrompt(input.context) },\n { type: \"image_url\", image_url: { url: `data:${input.mimeType};base64,${image.toString(\"base64\")}` } },\n ],\n },\n ],\n response_format: { type: \"json_object\" },\n temperature: this.options.temperature ?? 0.2,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`多模态请求失败:${response.status} ${body}`);\n }\n\n const data = (await response.json()) as MultimodalCompletionResponse;\n const content = data.choices?.[0]?.message?.content?.trim();\n if (!content) {\n throw new Error(\"多模态模型返回为空。\");\n }\n\n return parseDescribeImageResult(content);\n }\n}\n\nexport function createMultimodalModel(config: AppConfig, secrets: AppSecrets): OpenAICompatibleMultimodalModel {\n return new OpenAICompatibleMultimodalModel({\n baseUrl: config.multimodal.baseUrl,\n apiKey: secrets.multimodal.apiKey,\n model: config.multimodal.model,\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 BuildEvidencePromptInput {\n question: string;\n evidence: EvidenceBlock[];\n now: Date;\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 input: BuildEvidencePromptInput,\n options: BuildEvidencePromptOptions = {},\n): EvidencePrompt {\n const { question, evidence, now } = input;\n\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] 这样的来源标记。证据不足时说不知道,不要猜。若证据互相矛盾,优先采用时间更新且表述明确的证据;如果较新的证据只是讨论、猜测或不确定表达,不要把它当作确定更新。检索证据中的时间戳是消息被发送时的真实时间。回答时若涉及相对时间表述(如消息中说“明天”“今晚”),必须基于证据中每条消息的时间戳推导为具体日期(如“2026-05-06”),不要照搬原文的相对表述。证据中每条消息标注了发送时间。回答时优先输出绝对日期,不确定时引用原文时间戳,不要使用“今天”“明天”等依赖当前上下文的模糊表述。\",\n },\n {\n role: \"user\",\n content: `当前时间:${now.toISOString()}\\n问题:${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 now: Date;\n}): Promise<GroundedAnswer> {\n const prompt = buildEvidencePrompt({ question: input.question, evidence: input.evidence, now: input.now });\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 now?: Date;\n}\n\nexport async function askWithRag(input: AskWithRagInput): Promise<GroundedAnswer> {\n const now = input.now ?? new Date();\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 now,\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 { 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 crypto from \"node:crypto\";\nimport Fastify, { type FastifyInstance } from \"fastify\";\nimport { loadSecrets, saveSecrets } from \"../config/store.js\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { CronJobRepository } from \"../cron/jobs.js\";\nimport { openDatabase } from \"../db/database.js\";\nimport { FileJobRepository } from \"../files/jobs.js\";\nimport { EpisodeRepository } from \"../episodes/repository.js\";\nimport { getGatewayStatus } from \"../gateway/index.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { processMessagesNow } from \"../rag/manual-index.js\";\nimport { QaLogRepository } from \"../rag/qa-logs.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 <section>\n <h2>会话记忆</h2>\n <div id=\"episodes\" class=\"empty\">正在读取...</div>\n </section>\n <section>\n <h2>问答日志</h2>\n <div id=\"qa-logs\" 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 <div id=\"cron-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 episodes = document.querySelector(\"#episodes\");\n const chats = document.querySelector(\"#chats\");\n const files = document.querySelector(\"#files\");\n const fileJobs = document.querySelector(\"#file-jobs\");\n const cronJobs = document.querySelector(\"#cron-jobs\");\n const qaLogs = document.querySelector(\"#qa-logs\");\n const processMessages = document.querySelector(\"#process-messages\");\n const actionStatus = document.querySelector(\"#action-status\");\n\n let webActionToken = \"__WEB_ACTION_TOKEN__\";\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.version || \"unknown\", \"当前运行版本\", \"\"],\n [\"群聊\", status.data.chats, \"本地群聊数\", \"\"],\n [\"消息\", status.data.messages, \"已入库消息\", \"\"],\n [\"会话记忆\", status.data.episodes, \"已生成摘要\", \"\"],\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 renderEpisodes(items) {\n if (items.length === 0) {\n episodes.className = \"empty\";\n episodes.textContent = \"还没有会话记忆。默认在 10 分钟窗口静默 2 分钟后生成,也可以运行 chattercatcher process episodes 手动触发。\";\n return;\n }\n episodes.className = \"\";\n episodes.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.startedAt))} - \\${escapeHtml(formatDateTime(item.endedAt))}</span>\n <span>\\${escapeHtml(displayChatName(item.chatName, \"feishu\"))}</span>\n <span>\\${escapeHtml(item.messageCount)} 条消息</span>\n </div>\n <div class=\"message-body\">\\${escapeHtml(item.summary)}</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 function renderCronJobs(items) {\n if (items.length === 0) {\n cronJobs.className = \"empty\";\n cronJobs.textContent = \"还没有定时任务。可在飞书群里 @ 机器人创建。\";\n return;\n }\n cronJobs.className = \"\";\n cronJobs.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.schedule)}</div>\n <div class=\"message\" title=\"\\${escapeHtml(item.prompt)}\">\\${escapeHtml(item.prompt)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.id)}\">ID: \\${escapeHtml(item.id)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.chatId)}\">群: \\${escapeHtml(item.chatId)}</div>\n <div class=\"path\">下次: \\${escapeHtml(formatDateTime(item.nextRunAt))}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.lastError || \"\")}\">\\${escapeHtml(item.lastError || \"\")}</div>\n \\${item.status === \"active\" ? \\`<button type=\"button\" data-delete-cron-job=\"\\${escapeHtml(item.id)}\">删除</button>\\` : \"\"}\n </td>\n <td>\\${escapeHtml(item.status)}</td>\n </tr>\n \\`).join(\"\")}\n </tbody>\n </table>\n \\`;\n }\n\n function renderQaLogs(items) {\n if (items.length === 0) {\n qaLogs.className = \"empty\";\n qaLogs.textContent = \"还没有问答日志。\";\n return;\n }\n qaLogs.className = \"\";\n const rows = items.map((item) => {\n const citationCount = Array.isArray(item.citations) ? item.citations.length : 0;\n return [\n '<article class=\"message-item\">',\n ' <div class=\"message-meta\">',\n \" <span>\" + escapeHtml(formatDateTime(item.createdAt)) + \"</span>\",\n \" <span>\" + escapeHtml(item.status) + \"</span>\",\n \" <span>\" + escapeHtml(citationCount) + \" 条引用</span>\",\n \" </div>\",\n \" <div class=\\\\\\\"message-body\\\\\\\"><strong>问:</strong>\" + escapeHtml(item.question) + \"</div>\",\n \" <div class=\\\\\\\"message-body\\\\\\\"><strong>答:</strong>\" + escapeHtml(item.answer) + \"</div>\",\n \"</article>\",\n ].join(\"\\\\n\");\n });\n qaLogs.innerHTML = [\n '<div class=\"message-list\">',\n rows.join(\"\"),\n \"</div>\",\n ].join(\"\\\\n\");\n }\n\n async function fetchJson(path) {\n const response = await fetch(path);\n if (!response.ok) {\n const body = await response.text();\n throw new Error(path + \" \" + response.status + \" \" + body);\n }\n return response.json();\n }\n\n function renderLoadError(element, error) {\n element.className = \"empty\";\n element.textContent = \"加载失败:\" + (error instanceof Error ? error.message : String(error));\n }\n\n async function loadSection(path, element, render) {\n try {\n render(await fetchJson(path));\n } catch (error) {\n renderLoadError(element, error);\n }\n }\n\n async function load() {\n await Promise.all([\n loadSection(\"/api/status\", metrics, renderMetrics),\n loadSection(\"/api/messages/recent?limit=20\", messages, (data) => renderMessages(data.items)),\n loadSection(\"/api/episodes?limit=10\", episodes, (data) => renderEpisodes(data.items)),\n loadSection(\"/api/chats\", chats, (data) => renderChats(data.items)),\n loadSection(\"/api/files\", files, (data) => renderFiles(data.items)),\n loadSection(\"/api/file-jobs\", fileJobs, (data) => renderFileJobs(data.items)),\n loadSection(\"/api/qa-logs?limit=10\", qaLogs, (data) => renderQaLogs(data.items)),\n loadSection(\"/api/cron-jobs\", cronJobs, (data) => renderCronJobs(data.items)),\n ]);\n }\n\n async function processNow() {\n processMessages.disabled = true;\n actionStatus.textContent = \"正在处理消息索引...\";\n try {\n const response = await fetch(\"/api/process/messages\", {\n method: \"POST\",\n headers: { \"x-chattercatcher-web-token\": webActionToken },\n });\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 document.addEventListener(\"click\", async (event) => {\n const target = event.target;\n if (!(target instanceof HTMLElement)) return;\n const id = target.dataset.deleteCronJob;\n if (!id) return;\n target.setAttribute(\"disabled\", \"disabled\");\n actionStatus.textContent = \"正在删除定时任务...\";\n try {\n const response = await fetch(\\`/api/cron-jobs/\\${encodeURIComponent(id)}\\`, {\n method: \"DELETE\",\n headers: { \"x-chattercatcher-web-token\": webActionToken },\n });\n const result = await response.json();\n actionStatus.textContent = result.ok ? \"定时任务已删除。\" : result.message || \"删除失败。\";\n await load();\n } catch (error) {\n actionStatus.textContent = error instanceof Error ? error.message : String(error);\n } finally {\n target.removeAttribute(\"disabled\");\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\nexport interface WebAppOptions {\n version?: string;\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\nfunction getWebActionToken(secrets: Awaited<ReturnType<typeof loadSecrets>>): string {\n return secrets.web.actionToken;\n}\n\nfunction readHeader(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction isAuthorizedWebAction(request: { headers: Record<string, string | string[] | undefined> }, token: string): boolean {\n const provided = readHeader(request.headers[\"x-chattercatcher-web-token\"]);\n return provided === token;\n}\n\n\nexport function createWebApp(config: AppConfig, options: WebAppOptions = {}): FastifyInstance {\n const app = Fastify({ logger: false });\n const database = openDatabase(config);\n const version = options.version ?? \"unknown\";\n const messages = new MessageRepository(database);\n const episodes = new EpisodeRepository(database);\n const fileJobs = new FileJobRepository(database);\n const qaLogs = new QaLogRepository(database);\n const cronJobs = new CronJobRepository(database);\n let webActionToken = \"\";\n const tokenReady = (async () => {\n const secrets = await loadSecrets();\n if (!secrets.web.actionToken) {\n secrets.web.actionToken = crypto.randomBytes(32).toString(\"hex\");\n await saveSecrets(secrets);\n }\n webActionToken = getWebActionToken(secrets);\n })();\n\n app.addHook(\"onClose\", async () => {\n database.close();\n });\n\n app.get(\"/api/status\", async () => {\n await tokenReady;\n return {\n app: \"ChatterCatcher\",\n version,\n gateway: getGatewayStatus(config),\n data: {\n chats: messages.getChatCount(),\n messages: messages.getMessageCount(),\n episodes: episodes.getEpisodeCount(),\n files: messages.listFiles(1_000).length,\n qaLogs: qaLogs.getCount(),\n cronJobs: cronJobs.list(1_000).length,\n },\n rag: {\n mode: \"required\",\n note: \"问答必须先检索证据,禁止全量上下文堆叠。\",\n retrieval: {\n keyword: \"SQLite FTS5\",\n vector: \"SQLite embedding\",\n hybrid: true,\n },\n },\n web: config.web,\n };\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.get(\"/api/episodes\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 20, 100);\n return {\n items: episodes.listRecentEpisodes(limit),\n };\n });\n\n app.get(\"/api/qa-logs\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 20, 100);\n return {\n items: qaLogs.listRecent(limit),\n };\n });\n\n app.get(\"/api/cron-jobs\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 50, 200);\n return {\n items: cronJobs.list(limit),\n };\n });\n\n app.delete(\"/api/cron-jobs/:id\", async (request, reply) => {\n await tokenReady;\n if (!isAuthorizedWebAction(request, webActionToken)) {\n reply.code(403);\n return { ok: false, message: \"Web 操作未授权。\" };\n }\n\n const id = (request.params as { id: string }).id;\n const job = cronJobs.get(id);\n if (!job) {\n reply.code(404);\n return { ok: false, message: \"没有找到定时任务。\" };\n }\n\n const ok = cronJobs.deleteByChat(id, job.chatId);\n return { ok };\n });\n\n app.post(\"/api/process/messages\", async (request, reply) => {\n await tokenReady;\n if (!isAuthorizedWebAction(request, webActionToken)) {\n reply.code(403);\n return { status: \"failed\", message: \"Web 操作未授权。\" };\n }\n\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 await tokenReady;\n reply.type(\"text/html; charset=utf-8\");\n return buildHtml().replaceAll(\"__WEB_ACTION_TOKEN__\", webActionToken);\n });\n\n return app;\n}\n\nexport async function startWebServer(config: AppConfig, options: WebAppOptions = {}): Promise<void> {\n const app = createWebApp(config, options);\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 const versionText = options.version ? ` ${options.version}` : \"\";\n console.log(`ChatterCatcher Web UI${versionText}: ${url}`);\n}\n"],"mappings":"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;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;AAAA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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","fs","path","path","fs","files","input","path","fs","data","crypto","nowIso","crypto","index","input","crypto","nowIso","stableId","crypto","escapeFtsQuery","buildScopeWhere","input","index","input","input","index","toEvidenceSource","buildScopeWhere","input","input","fs","fs","path","path","input","fs","fs","path","data","input","input","lark","crypto","input","crypto","index","input","parseCronSchedule","matchesParsedSchedule","parseMinuteField","parseExactOrWildcardField","parseExactNumber","input","index","toEvidenceSource","input","crypto","nowIso","stableId","input","input","crypto","input","index","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","fs","normalizeBaseUrl","data","input","input","index","input","input","index","crypto","crypto","files","fs"]}
1
+ {"version":3,"sources":["../src/cli.ts","../package.json","../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/logs/reader.ts","../src/gateway/index.ts","../src/llm/openai-compatible.ts","../src/messages/repository.ts","../src/messages/chunker.ts","../src/episodes/repository.ts","../src/episodes/sanitizer.ts","../src/rag/episode-retriever.ts","../src/rag/hybrid-retriever.ts","../src/rag/message-retriever.ts","../src/rag/search-tools.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/episodes/summarizer.ts","../src/episodes/manual-process.ts","../src/feishu/bot-info.ts","../src/feishu/gateway.ts","../src/cron/jobs.ts","../src/cron/schedule.ts","../src/cron/generator.ts","../src/cron/scheduler.ts","../src/gateway/indexing-scheduler.ts","../src/rag/indexer.ts","../src/rag/manual-index.ts","../src/multimodal/tasks.ts","../src/multimodal/worker.ts","../src/cron/tools.ts","../src/rag/qa-logs.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/multimodal/openai-compatible.ts","../src/rag/answer.ts","../src/rag/qa-service.ts","../src/rag/citations.ts","../src/update/npm-updater.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 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 { processEpisodesNow } from \"./episodes/manual-process.js\";\nimport { ensureFeishuBotOpenId } from \"./feishu/bot-info.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 { createMultimodalModel } from \"./multimodal/openai-compatible.js\";\nimport { followLogFile, getLogsDirectory, normalizeLineCount, readLatestLogTail } from \"./logs/reader.js\";\nimport { MessageRepository } from \"./messages/repository.js\";\nimport { indexMessageChunks } from \"./rag/indexer.js\";\nimport { createHybridRetriever, hasEmbeddingConfig } from \"./rag/factory.js\";\nimport { processMessagesNow } from \"./rag/manual-index.js\";\nimport { SqliteVectorStore } from \"./rag/sqlite-vector-store.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 await tryEnsureFeishuBotOpenId(config, secrets);\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 multimodalBaseUrl = await input({\n message: \"Multimodal Base URL(OpenAI-compatible,可留空)\",\n default: config.multimodal.baseUrl,\n });\n const multimodalApiKey = await password({\n message: \"Multimodal API Key(可留空)\",\n mask: \"*\",\n });\n const multimodalModel = await input({\n message: \"Multimodal Model(可留空)\",\n default: config.multimodal.model,\n });\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 config.multimodal = {\n baseUrl: multimodalBaseUrl,\n model: multimodalModel,\n };\n\n secrets.multimodal = {\n apiKey: multimodalApiKey || secrets.multimodal.apiKey,\n };\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: \"群聊回答是否要求 @ 机器人?\",\n default: config.feishu.requireMention,\n });\n config.episodes.windowMinutes =\n (await number({ message: \"会话记忆聚合窗口(分钟)\", default: config.episodes.windowMinutes, required: true })) ??\n config.episodes.windowMinutes;\n config.episodes.quietMinutes =\n (await number({ message: \"会话静默多久后生成记忆(分钟)\", default: config.episodes.quietMinutes, required: true })) ??\n config.episodes.quietMinutes;\n}\n\nasync function tryEnsureFeishuBotOpenId(config: AppConfig, secrets: AppSecrets): Promise<void> {\n if (config.feishu.botOpenId || !config.feishu.appId || !secrets.feishu.appSecret) {\n return;\n }\n\n try {\n const openId = await ensureFeishuBotOpenId(config, secrets, { onSave: () => saveConfig(config) });\n console.log(`已自动获取飞书机器人 Open ID:${openId}`);\n } catch (error) {\n console.log(`暂时无法自动获取飞书机器人 Open ID:${error instanceof Error ? error.message : String(error)}`);\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 multimodal: { apiKey: maskSecret(secrets.multimodal.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, { version: packageJson.version });\n return;\n }\n\n await tryEnsureFeishuBotOpenId(config, secrets);\n\n writeGatewayPidRecord(undefined, {\n ...pidRecordBase,\n mode: \"gateway\",\n });\n\n const database = openDatabase(config);\n const chatModel = createChatModel(config, secrets);\n const sender = FeishuMessageSender.fromConfig(config, secrets);\n const vectorStore = hasEmbeddingConfig(config, secrets)\n ? new SqliteVectorStore(database, { model: config.embedding.model })\n : 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 episodeProcessor: {\n database,\n model: chatModel,\n },\n imageMultimodalProcessor:\n config.multimodal.baseUrl && config.multimodal.model && secrets.multimodal.apiKey\n ? {\n database,\n model: createMultimodalModel(config, secrets),\n }\n : undefined,\n indexingProcessor: {\n database,\n },\n cronJobProcessor: {\n database,\n model: chatModel,\n sender,\n },\n questionHandler: new FeishuQuestionHandler({\n config,\n secrets,\n database,\n sender,\n model: chatModel,\n }),\n });\n\n const cleanup = () => {\n gatewayRuntime.stop();\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, { version: packageJson.version });\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 await tryEnsureFeishuBotOpenId(config, secrets);\n const result = await 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, { version: packageJson.version });\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 已同步删除;如使用 SQLite embedding 语义检索,请运行 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\n try {\n const messages = new MessageRepository(database);\n const vectorStore = new SqliteVectorStore(database, { model: config.embedding.model });\n const vectors = vectorStore.count();\n console.log(JSON.stringify(\n {\n database: getDatabasePath(config),\n chats: messages.getChatCount(),\n messages: messages.getMessageCount(),\n embeddings: {\n backend: \"SQLite embedding 向量索引\",\n configured: hasEmbeddingConfig(config, secrets),\n model: config.embedding.model,\n vectors,\n status: hasEmbeddingConfig(config, secrets)\n ? \"SQLite embedding 向量索引已可用于语义检索\"\n : \"SQLite embedding 向量索引已接入;需配置 embedding 后启用语义检索\",\n },\n retrieval: {\n keyword: \"SQLite FTS5\",\n vector: \"SQLite embedding 向量索引\",\n hybrid: \"启用:SQLite FTS + SQLite embedding 向量检索\",\n rag: \"强制先检索证据再回答,禁止全量上下文堆叠\",\n },\n },\n null,\n 2,\n ));\n } finally {\n database.close();\n }\n});\n\nindex.command(\"rebuild\").description(\"重建语义向量索引\").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 配置不完整,无法重建 SQLite embedding 向量索引。请运行 chattercatcher setup 或 chattercatcher settings。\");\n return;\n }\n\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\n if (result.status === \"skipped\") {\n console.log(`处理跳过:${result.reason}`);\n return;\n }\n\n console.log(`SQLite embedding 向量索引完成:chunks=${result.chunks}, vectors=${result.vectors}`);\n } finally {\n database.close();\n }\n});\n\nconst processCommand = program.command(\"process\").description(\"立即处理后台任务\");\n\nprocessCommand\n .command(\"messages\")\n .description(\"立即处理消息索引任务,把消息 chunks 写入 SQLite embedding 向量索引\")\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\nprocessCommand\n .command(\"episodes\")\n .description(\"立即生成会话记忆块,把碎片化闲聊整理成可检索摘要\")\n .action(async () => {\n const config = await loadConfig();\n const secrets = await loadSecrets();\n const database = openDatabase(config);\n\n try {\n const result = await processEpisodesNow({\n config,\n secrets,\n database,\n model: createChatModel(config, secrets),\n });\n console.log(`会话记忆处理完成:episodes=${result.created}`);\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 embedding 向量索引。\");\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 已重建;如使用 SQLite embedding 语义检索,请运行 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 database,\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 database,\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.27\",\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/DEVELOPMENT_PLAN.md\",\n \"docs/PRD.md\",\n \"docs/TECHNICAL_ARCHITECTURE.md\",\n \"README.md\",\n \"AGENTS.md\"\n ],\n \"directories\": {\n \"doc\": \"docs\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"prepack\": \"npm run build\",\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 \"@larksuiteoapi/node-sdk\": \"^1.62.1\",\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 botOpenId: 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 multimodal: z.preprocess(\n (value) => value ?? {},\n z.object({\n baseUrl: z.string().url().or(z.literal(\"\")).default(\"\"),\n model: z.string().default(\"\"),\n }),\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 episodes: z\n .object({\n windowMinutes: z.number().int().positive().default(10),\n quietMinutes: z.number().int().positive().default(2),\n })\n .default({ windowMinutes: 10, quietMinutes: 2 }),\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 multimodal: z.preprocess(\n (value) => value ?? {},\n z.object({\n apiKey: z.string().default(\"\"),\n }),\n ),\n web: z.preprocess(\n (value) => value ?? {},\n z.object({\n actionToken: z.string().default(\"\"),\n }),\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 multimodal: {},\n storage: {},\n web: {},\n schedules: {},\n episodes: {},\n });\n}\n\nexport function createDefaultSecrets(): AppSecrets {\n return appSecretsSchema.parse({\n feishu: {},\n llm: {},\n embedding: {},\n multimodal: {},\n web: {},\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 memory_episodes (\n id TEXT PRIMARY KEY,\n chat_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,\n summary TEXT NOT NULL,\n message_count INTEGER NOT NULL,\n started_at TEXT NOT NULL,\n ended_at TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE(chat_id, started_at, ended_at)\n );\n\n CREATE TABLE IF NOT EXISTS memory_episode_messages (\n episode_id TEXT NOT NULL REFERENCES memory_episodes(id) ON DELETE CASCADE,\n message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n position INTEGER NOT NULL,\n PRIMARY KEY (episode_id, message_id)\n );\n\n CREATE VIRTUAL TABLE IF NOT EXISTS memory_episodes_fts USING fts5(\n summary,\n episode_id UNINDEXED,\n tokenize = 'unicode61'\n );\n\n CREATE TRIGGER IF NOT EXISTS memory_episodes_delete_fts\n AFTER DELETE ON memory_episodes\n BEGIN\n DELETE FROM memory_episodes_fts WHERE episode_id = old.id;\n END;\n\n CREATE INDEX IF NOT EXISTS memory_episode_messages_message_idx\n ON memory_episode_messages(message_id);\n\n CREATE TABLE IF NOT EXISTS message_chunk_embeddings (\n chunk_id TEXT NOT NULL REFERENCES message_chunks(id) ON DELETE CASCADE,\n model TEXT NOT NULL,\n dimension INTEGER NOT NULL,\n embedding_json TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (chunk_id, model)\n );\n\n CREATE INDEX IF NOT EXISTS message_chunk_embeddings_model_idx\n ON message_chunk_embeddings(model, dimension);\n\n CREATE TABLE IF NOT EXISTS qa_logs (\n id TEXT PRIMARY KEY,\n chat_id TEXT,\n question_message_id TEXT,\n question TEXT NOT NULL,\n answer TEXT NOT NULL,\n citations_json TEXT NOT NULL,\n retrieval_debug_json TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('answered','failed')),\n error TEXT,\n created_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS qa_logs_created_at_idx ON qa_logs(created_at);\n CREATE INDEX IF NOT EXISTS qa_logs_chat_idx ON qa_logs(chat_id, created_at);\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 CREATE TABLE IF NOT EXISTS image_multimodal_tasks (\n id TEXT PRIMARY KEY,\n source_message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n platform_message_id TEXT NOT NULL,\n image_key TEXT NOT NULL,\n stored_path TEXT NOT NULL,\n mime_type TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('pending','running','succeeded','skipped','failed')),\n attempts INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n derived_message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n UNIQUE(source_message_id, image_key)\n );\n\n CREATE INDEX IF NOT EXISTS image_multimodal_tasks_status_idx ON image_multimodal_tasks(status, updated_at);\n\n CREATE TABLE IF NOT EXISTS cron_jobs (\n id TEXT PRIMARY KEY,\n chat_id TEXT NOT NULL,\n created_by_open_id TEXT,\n schedule TEXT NOT NULL,\n prompt TEXT NOT NULL,\n status TEXT NOT NULL CHECK(status IN ('active','deleted')),\n last_run_at TEXT,\n next_run_at TEXT NOT NULL,\n last_error TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n image_file_name TEXT\n );\n\n CREATE INDEX IF NOT EXISTS cron_jobs_chat_status_idx ON cron_jobs(chat_id, status, updated_at);\n CREATE INDEX IF NOT EXISTS cron_jobs_due_idx ON cron_jobs(status, next_run_at);\n `);\n\n const cronJobColumns = database.prepare(\"PRAGMA table_info(cron_jobs)\").all() as Array<{ name: string }>;\n if (!cronJobColumns.some((column) => column.name === \"image_file_name\")) {\n database.prepare(\"ALTER TABLE cron_jobs ADD COLUMN image_file_name TEXT\").run();\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 checkSqliteVectorIndex(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 embedding 语义检索。\");\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 checkSqliteVectorIndex(config: AppConfig): Promise<DoctorCheck> {\n let database: ReturnType<typeof openDatabase> | null = null;\n try {\n database = openDatabase(config);\n const defaultModel = config.embedding.model || \"default\";\n const vectorStore = new SqliteVectorStore(database, { model: defaultModel });\n const vectors = vectorStore.count();\n const availableModels = database\n .prepare(\"SELECT COUNT(DISTINCT model) AS count FROM message_chunk_embeddings\")\n .get() as { count: number };\n\n return pass(\n \"SQLite embedding 向量索引\",\n `${getDatabasePath(config)};vectors=${vectors};models=${availableModels.count}${config.embedding.model ? `;active_model=${config.embedding.model}` : \";active_model=未配置\"}`,\n );\n } catch (error) {\n return fail(\"SQLite embedding 向量索引\", 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\";\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, ChatTool, ToolCall, ToolChatResult } from \"../rag/types.js\";\n\nexport interface OpenAICompatibleChatOptions {\n baseUrl: string;\n apiKey: string;\n model: string;\n temperature?: number;\n}\n\ninterface OpenAICompatibleMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string;\n tool_call_id?: string;\n tool_calls?: Array<{\n id: string;\n type: \"function\";\n function: {\n name: string;\n arguments: string;\n };\n }>;\n}\n\ninterface ChatCompletionResponse {\n choices?: Array<{\n message?: OpenAICompatibleMessage & { reasoning_content?: string };\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\nfunction toOpenAIMessage(message: ChatMessage): OpenAICompatibleMessage {\n return {\n role: message.role,\n content: message.content,\n ...(message.toolCallId ? { tool_call_id: message.toolCallId } : {}),\n ...(message.toolCalls\n ? {\n tool_calls: message.toolCalls.map((toolCall) => ({\n id: toolCall.id,\n type: \"function\" as const,\n function: {\n name: toolCall.name,\n arguments: JSON.stringify(toolCall.input),\n },\n })),\n }\n : {}),\n ...(message.reasoningContent ? { reasoning_content: message.reasoningContent } : {}),\n };\n}\n\nfunction toOpenAITool(tool: ChatTool): {\n type: \"function\";\n function: {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n };\n} {\n return {\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.inputSchema,\n },\n };\n}\n\nfunction parseToolCalls(message?: OpenAICompatibleMessage): ToolCall[] {\n return (\n message?.tool_calls?.map((toolCall) => ({\n id: toolCall.id,\n name: toolCall.function.name,\n input: JSON.parse(toolCall.function.arguments),\n })) ?? []\n );\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: messages.map(toOpenAIMessage),\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 message = data.choices?.[0]?.message;\n const content = message?.content?.trim();\n if (!content) {\n throw new Error(\"LLM 返回为空。\");\n }\n\n return content;\n }\n\n async completeWithTools(messages: ChatMessage[], tools: ChatTool[]): Promise<ToolChatResult> {\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: messages.map(toOpenAIMessage),\n tools: tools.map(toOpenAITool),\n tool_choice: \"auto\",\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 message = data.choices?.[0]?.message;\n\n return {\n content: message?.content ?? \"\",\n toolCalls: parseToolCalls(message),\n reasoningContent: message?.reasoning_content ?? undefined,\n };\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 {\n ChatRecord,\n CreateImageSummaryMessageInput,\n FileRecord,\n IngestMessageInput,\n MessageSearchResult,\n MessageSearchScope,\n} 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 buildScopeWhere(scope?: MessageSearchScope): { where: string; params: string[] } {\n const clauses: string[] = [];\n const params: string[] = [];\n if (scope?.platform) {\n clauses.push(\"m.platform = ?\");\n params.push(scope.platform);\n }\n if (scope?.platformChatId) {\n clauses.push(\"c.platform_chat_id = ?\");\n params.push(scope.platformChatId);\n }\n\n return {\n where: clauses.length > 0 ? `AND ${clauses.join(\" AND \")}` : \"\",\n params,\n };\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 message_type = excluded.message_type,\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 createImageSummaryMessage(input: CreateImageSummaryMessageInput): string {\n const source = this.database\n .prepare(\n `\n SELECT\n m.platform AS platform,\n m.platform_message_id AS platformMessageId,\n m.chat_id AS chatId,\n m.sender_id AS senderId,\n m.sender_name AS senderName,\n m.sent_at AS sentAt,\n c.platform_chat_id AS platformChatId,\n c.name AS chatName\n FROM messages m\n JOIN chats c ON c.id = m.chat_id\n WHERE m.id = ?\n `,\n )\n .get(input.sourceMessageId) as\n | {\n platform: string;\n platformMessageId: string;\n chatId: string;\n senderId: string;\n senderName: string;\n sentAt: string;\n platformChatId: string;\n chatName: string;\n }\n | undefined;\n\n if (!source) {\n throw new Error(\"原始图片消息不存在。\");\n }\n\n const derivedPlatformMessageId = `${source.platformMessageId}:image-summary:${input.imageKey}`;\n const imageFileName = input.imageFileName?.trim();\n const summaryText = imageFileName\n ? `[图片转述] 文件名:${imageFileName}\\n${input.summary.trim()}`\n : `[图片转述] ${input.summary.trim()}`;\n return this.ingest({\n platform: source.platform,\n platformChatId: source.platformChatId,\n chatName: source.chatName,\n platformMessageId: derivedPlatformMessageId,\n senderId: source.senderId,\n senderName: source.senderName,\n messageType: \"image_summary\",\n text: summaryText,\n sentAt: source.sentAt,\n rawPayload: {\n derivedFromMessageId: input.sourceMessageId,\n sourceAttachmentKind: \"image\",\n sourceResourceKey: input.imageKey,\n ...(imageFileName ? { imageFileName } : {}),\n multimodalModel: input.multimodalModel,\n isMeaningful: true,\n ...(input.reason?.trim() ? { reason: input.reason.trim() } : {}),\n generatedAt: input.generatedAt,\n },\n });\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[]; scope?: MessageSearchScope } = {}): 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 scope = buildScopeWhere(options.scope);\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 ${scope.where}\n ORDER BY bm25(message_chunks_fts)\n LIMIT ?\n `,\n )\n .all(ftsQuery, ...excludedIds, ...scope.params, 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 ${scope.where}\n ORDER BY m.sent_at DESC\n LIMIT ?\n `,\n )\n .all(...params, ...excludedIds, ...scope.params, 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 crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport type { MessageSearchResult, MessageSearchScope } from \"../messages/types.js\";\nimport { sanitizeEpisodeSummary } from \"./sanitizer.js\";\n\nexport interface EpisodeMessage {\n id: string;\n chatId: string;\n chatName: string;\n senderName: string;\n text: string;\n sentAt: string;\n}\n\nexport interface EpisodeWindow {\n chatId: string;\n chatName: string;\n startedAt: string;\n endedAt: string;\n messages: EpisodeMessage[];\n}\n\nexport interface EpisodeSummaryRecord {\n id: string;\n chatId: string;\n chatName: string;\n text: string;\n startedAt: string;\n endedAt: string;\n messageIds: string[];\n}\n\nexport interface EpisodeSearchResult extends MessageSearchResult {\n sourceMessageIds: string[];\n startedAt: string;\n endedAt: string;\n}\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(/[^\\p{L}\\p{N}_-]+/gu, \" \").trim())\n .flatMap((term) => term.split(/\\s+/))\n .filter(Boolean);\n\n if (terms.length === 0) {\n return \"\\\"\\\"\";\n }\n\n return terms.map((term) => `\"${term.replace(/\"/g, '\"\"')}\"`).join(\" OR \");\n}\n\nfunction toMillis(value: string): number {\n const time = Date.parse(value);\n return Number.isFinite(time) ? time : 0;\n}\n\nfunction buildScopeWhere(scope?: MessageSearchScope): { where: string; params: string[] } {\n const clauses: string[] = [];\n const params: string[] = [];\n if (scope?.platform) {\n clauses.push(\"c.platform = ?\");\n params.push(scope.platform);\n }\n if (scope?.platformChatId) {\n clauses.push(\"c.platform_chat_id = ?\");\n params.push(scope.platformChatId);\n }\n\n return {\n where: clauses.length > 0 ? `AND ${clauses.join(\" AND \")}` : \"\",\n params,\n };\n}\n\nexport interface EpisodeListItem {\n id: string;\n chatId: string;\n chatName: string;\n summary: string;\n messageCount: number;\n startedAt: string;\n endedAt: string;\n createdAt: string;\n}\n\nexport class EpisodeRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n async summarizeReadyWindows(input: {\n now: Date;\n quietMs: number;\n windowMs: number;\n summarize: (window: EpisodeWindow, now: Date) => Promise<string>;\n }): Promise<EpisodeSummaryRecord[]> {\n const rows = this.database\n .prepare(\n `\n SELECT\n m.id,\n m.chat_id AS chatId,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.text,\n m.sent_at AS sentAt\n FROM messages m\n JOIN chats c ON c.id = m.chat_id\n WHERE NOT EXISTS (\n SELECT 1 FROM memory_episode_messages mem WHERE mem.message_id = m.id\n )\n ORDER BY m.chat_id ASC, m.sent_at ASC\n `,\n )\n .all() as EpisodeMessage[];\n\n const byChat = new Map<string, EpisodeMessage[]>();\n for (const row of rows) {\n byChat.set(row.chatId, [...(byChat.get(row.chatId) ?? []), row]);\n }\n\n const created: EpisodeSummaryRecord[] = [];\n const nowMs = input.now.getTime();\n for (const messages of byChat.values()) {\n const windows: EpisodeMessage[][] = [];\n let current: EpisodeMessage[] = [];\n for (const message of messages) {\n const first = current[0];\n if (first && toMillis(message.sentAt) - toMillis(first.sentAt) > input.windowMs) {\n windows.push(current);\n current = [];\n }\n current.push(message);\n }\n if (current.length > 0) {\n windows.push(current);\n }\n\n for (const windowMessages of windows) {\n const last = windowMessages.at(-1);\n if (!last || nowMs - toMillis(last.sentAt) < input.quietMs) {\n continue;\n }\n\n const first = windowMessages[0]!;\n const window: EpisodeWindow = {\n chatId: first.chatId,\n chatName: first.chatName,\n startedAt: first.sentAt,\n endedAt: last.sentAt,\n messages: windowMessages,\n };\n const summary = await input.summarize(window, input.now);\n created.push(this.insertEpisode(window, summary));\n }\n }\n\n return created;\n }\n\n private insertEpisode(window: EpisodeWindow, summary: string): EpisodeSummaryRecord {\n const safeSummary = sanitizeEpisodeSummary(summary);\n const createdAt = nowIso();\n const id = stableId([window.chatId, window.startedAt, window.endedAt]);\n const transaction = this.database.transaction(() => {\n this.database\n .prepare(\n `\n INSERT INTO memory_episodes (id, chat_id, summary, message_count, started_at, ended_at, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(chat_id, started_at, ended_at)\n DO UPDATE SET summary = excluded.summary, message_count = excluded.message_count\n `,\n )\n .run(id, window.chatId, safeSummary, window.messages.length, window.startedAt, window.endedAt, createdAt);\n this.database.prepare(\"DELETE FROM memory_episode_messages WHERE episode_id = ?\").run(id);\n this.database.prepare(\"DELETE FROM memory_episodes_fts WHERE episode_id = ?\").run(id);\n\n const insertMessage = this.database.prepare(\n \"INSERT INTO memory_episode_messages (episode_id, message_id, position) VALUES (?, ?, ?)\",\n );\n for (const [index, message] of window.messages.entries()) {\n insertMessage.run(id, message.id, index);\n }\n this.database.prepare(\"INSERT INTO memory_episodes_fts (summary, episode_id) VALUES (?, ?)\").run(safeSummary, id);\n });\n\n transaction();\n return {\n id,\n chatId: window.chatId,\n chatName: window.chatName,\n text: safeSummary,\n startedAt: window.startedAt,\n endedAt: window.endedAt,\n messageIds: window.messages.map((message) => message.id),\n };\n }\n\n async refreshWindowForMessage(input: {\n messageId: string;\n windowMs: number;\n summarize: (window: EpisodeWindow) => Promise<string>;\n }): Promise<EpisodeSummaryRecord | undefined> {\n const target = this.database\n .prepare(\n `\n SELECT chat_id AS chatId, sent_at AS sentAt\n FROM messages\n WHERE id = ?\n `,\n )\n .get(input.messageId) as { chatId: string; sentAt: string } | undefined;\n\n if (!target) {\n return undefined;\n }\n\n const existingWindow = this.database\n .prepare(\n `\n SELECT e.started_at AS startedAt, e.ended_at AS endedAt\n FROM messages target\n JOIN messages source\n ON source.id = json_extract(target.raw_payload_json, '$.derivedFromMessageId')\n JOIN memory_episode_messages mem ON mem.message_id = source.id\n JOIN memory_episodes e ON e.id = mem.episode_id\n WHERE target.id = ?\n LIMIT 1\n `,\n )\n .get(input.messageId) as { startedAt: string; endedAt: string } | undefined;\n if (!existingWindow) {\n return undefined;\n }\n\n const messageTime = toMillis(target.sentAt);\n const windowStart = toMillis(existingWindow.startedAt);\n const windowEnd = Math.max(toMillis(existingWindow.endedAt), messageTime);\n\n const rows = this.database\n .prepare(\n `\n SELECT\n m.id,\n m.chat_id AS chatId,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.text,\n m.sent_at AS sentAt\n FROM messages m\n JOIN chats c ON c.id = m.chat_id\n WHERE m.chat_id = ?\n ORDER BY m.sent_at ASC\n `,\n )\n .all(target.chatId) as EpisodeMessage[];\n\n const windowMessages = rows.filter((message) => {\n const time = toMillis(message.sentAt);\n return time >= windowStart && time <= windowEnd;\n });\n const first = windowMessages[0];\n const last = windowMessages.at(-1);\n if (!first || !last) {\n return undefined;\n }\n\n const window: EpisodeWindow = {\n chatId: first.chatId,\n chatName: first.chatName,\n startedAt: first.sentAt,\n endedAt: last.sentAt,\n messages: windowMessages,\n };\n const summary = await input.summarize(window);\n return this.insertEpisode(window, summary);\n }\n\n getEpisodeCount(): number {\n const row = this.database.prepare(\"SELECT count(*) AS count FROM memory_episodes\").get() as { count: number };\n return row.count;\n }\n\n listRecentEpisodes(limit = 20): EpisodeListItem[] {\n return this.database\n .prepare(\n `\n SELECT\n e.id,\n e.chat_id AS chatId,\n c.name AS chatName,\n e.summary,\n e.message_count AS messageCount,\n e.started_at AS startedAt,\n e.ended_at AS endedAt,\n e.created_at AS createdAt\n FROM memory_episodes e\n JOIN chats c ON c.id = e.chat_id\n ORDER BY e.ended_at DESC\n LIMIT ?\n `,\n )\n .all(limit) as EpisodeListItem[];\n }\n\n searchEpisodes(query: string, limit = 8, scope?: MessageSearchScope): EpisodeSearchResult[] {\n const ftsQuery = escapeFtsQuery(query);\n const scopeWhere = buildScopeWhere(scope);\n return this.database\n .prepare(\n `\n SELECT\n e.id AS chunkId,\n e.id AS messageId,\n 'episode' AS platform,\n e.summary AS text,\n 1.0 AS score,\n 'episode' AS messageType,\n c.name AS chatName,\n '会话记忆' AS senderName,\n e.ended_at AS sentAt,\n e.started_at AS startedAt,\n e.ended_at AS endedAt,\n (\n SELECT json_group_array(message_id)\n FROM (\n SELECT message_id\n FROM memory_episode_messages\n WHERE episode_id = e.id\n ORDER BY position ASC\n )\n ) AS sourceMessageIdsJson\n FROM memory_episodes_fts fts\n JOIN memory_episodes e ON e.id = fts.episode_id\n JOIN chats c ON c.id = e.chat_id\n WHERE memory_episodes_fts MATCH ?\n ${scopeWhere.where}\n GROUP BY e.id\n ORDER BY e.ended_at DESC\n LIMIT ?\n `,\n )\n .all(ftsQuery, ...scopeWhere.params, limit)\n .map((row) => {\n const item = row as MessageSearchResult & {\n startedAt: string;\n endedAt: string;\n sourceMessageIdsJson: string;\n };\n return {\n ...item,\n sourceMessageIds: JSON.parse(item.sourceMessageIdsJson) as string[],\n };\n });\n }\n}\n","const SECRET_PATTERNS: Array<[RegExp, string]> = [\n [/-----BEGIN [^-]+ PRIVATE KEY-----[\\s\\S]*?-----END [^-]+ PRIVATE KEY-----/g, \"[REDACTED_SECRET]\"],\n [/(\\bAuthorization\\s*:\\s*Bearer\\s+)[A-Za-z0-9._~+/=-]{12,}/gi, \"$1[REDACTED_SECRET]\"],\n [/(https?:\\/\\/)[^\\s/@:]+:[^\\s/@]+@/gi, \"$1[REDACTED_SECRET]@\"],\n [/([?&](?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|session(?:id)?|client[_-]?secret)=)[^\\s&,。;;]+/gi, \"$1[REDACTED_SECRET]\"],\n [/(\"(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|session(?:id)?|client[_-]?secret|private[_-]?key)\"\\s*:\\s*\")[^\"]+(\")/gi, \"$1[REDACTED_SECRET]$2\"],\n [/(\\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|session(?:id)?|client[_-]?secret)\\s*[=:]\\s*)[^\\s;,。]+/gi, \"$1[REDACTED_SECRET]\"],\n [/\\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\\b/g, \"[REDACTED_SECRET]\"],\n [/\\bxox[baprs]-[A-Za-z0-9-]{20,}\\b/g, \"[REDACTED_SECRET]\"],\n [/\\bsk-[A-Za-z0-9_-]{6,}\\b/g, \"[REDACTED_SECRET]\"],\n];\n\nexport function sanitizeEpisodeSummary(summary: string): string {\n let sanitized = summary;\n for (const [pattern, replacement] of SECRET_PATTERNS) {\n sanitized = sanitized.replace(pattern, replacement);\n }\n return sanitized;\n}\n","import { EpisodeRepository } from \"../episodes/repository.js\";\nimport type { EpisodeSearchResult } from \"../episodes/repository.js\";\nimport type { EvidenceBlock } from \"./types.js\";\nimport type { Retriever, RetrievalScope } from \"./retriever.js\";\n\nfunction toEpisodeEvidence(result: EpisodeSearchResult): EvidenceBlock {\n return {\n id: result.chunkId,\n text: result.text,\n score: result.score,\n source: {\n type: \"episode\",\n label: result.chatName,\n sender: result.senderName,\n timestamp: result.endedAt,\n location: `${result.startedAt} - ${result.endedAt}`,\n },\n };\n}\n\nexport class EpisodeFtsRetriever implements Retriever {\n constructor(private readonly episodes: EpisodeRepository) {}\n\n async retrieve(question: string, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n return this.episodes.searchEpisodes(question, 8, scope).map(toEpisodeEvidence);\n }\n}\n","import type { Retriever, RetrievalScope } from \"./retriever.js\";\nimport type { EvidenceBlock } from \"./types.js\";\n\nexport interface HybridRetrieverOptions {\n limit?: number;\n scope?: RetrievalScope;\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\nfunction evidenceTimestampMs(evidence: EvidenceBlock): number {\n const timestamp = evidence.source.timestamp;\n if (!timestamp) {\n return 0;\n }\n\n const parsed = Date.parse(timestamp);\n return Number.isFinite(parsed) ? parsed : 0;\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, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n const effectiveScope = scope ?? this.options.scope;\n const results = await Promise.all(this.retrievers.map((retriever) => retriever.retrieve(question, effectiveScope)));\n const merged = new Map<string, EvidenceBlock>();\n\n for (const evidenceList of results) {\n for (const evidence of evidenceList) {\n const existing = merged.get(evidence.id);\n const score = normalizeScore(evidence.score);\n\n if (!existing || score > existing.score) {\n merged.set(evidence.id, {\n ...evidence,\n score,\n });\n }\n }\n }\n\n return [...merged.values()]\n .sort((left, right) => right.score - left.score || evidenceTimestampMs(right) - evidenceTimestampMs(left))\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, RetrievalScope } 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, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n const results = this.messages.searchMessages(question, 8, {\n excludeMessageIds: this.options.excludeMessageIds,\n scope,\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 { Retriever, RetrievalScope } from \"./retriever.js\";\nimport type { ChatTool, EvidenceBlock } from \"./types.js\";\n\nexport interface RagSearchTool extends ChatTool {\n execute(input: unknown): Promise<EvidenceBlock[]>;\n}\n\nexport interface CreateRagSearchToolsInput {\n hybrid: Retriever;\n messages: Retriever;\n episodes: Retriever;\n semantic?: Retriever;\n scope?: RetrievalScope;\n}\n\nconst searchInputSchema = {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Search query written by the model.\" },\n limit: { type: \"number\", description: \"Maximum number of evidence blocks to return.\" },\n },\n required: [\"query\"],\n additionalProperties: false,\n};\n\ninterface SearchInput {\n query: string;\n limit: number;\n}\n\nfunction parseSearchInput(input: unknown): SearchInput {\n const rawQuery =\n typeof input === \"object\" && input !== null && \"query\" in input\n ? (input as { query?: unknown }).query\n : undefined;\n\n if (typeof rawQuery !== \"string\") {\n throw new Error(\"搜索 query 必须是非空字符串。\");\n }\n\n const query = rawQuery.trim();\n if (!query) {\n throw new Error(\"搜索 query 必须是非空字符串。\");\n }\n\n const rawLimit =\n typeof input === \"object\" && input !== null && \"limit\" in input\n ? (input as { limit?: unknown }).limit\n : undefined;\n const numericLimit = typeof rawLimit === \"number\" && Number.isFinite(rawLimit) ? rawLimit : 5;\n const limit = Math.min(12, Math.max(1, Math.floor(numericLimit)));\n\n return { query, limit };\n}\n\nasync function runRetriever(retriever: Retriever, input: unknown, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n const { query, limit } = parseSearchInput(input);\n const results = await retriever.retrieve(query, scope);\n return results.slice(0, limit);\n}\n\nfunction createSearchTool(name: string, description: string, retriever: Retriever, scope?: RetrievalScope): RagSearchTool {\n return {\n name,\n description,\n inputSchema: searchInputSchema,\n execute: (input) => runRetriever(retriever, input, scope),\n };\n}\n\nexport async function executeRagSearchTool(tool: RagSearchTool, input: unknown): Promise<EvidenceBlock[]> {\n const { limit } = parseSearchInput(input);\n const results = await tool.execute(input);\n return results.slice(0, limit);\n}\n\nexport function createRagSearchTools(input: CreateRagSearchToolsInput): RagSearchTool[] {\n const tools: RagSearchTool[] = [\n createSearchTool(\n \"hybrid_search\",\n \"Search across all indexed RAG evidence using the default hybrid retrieval strategy.\",\n input.hybrid,\n input.scope,\n ),\n createSearchTool(\n \"search_messages\",\n \"Search chat messages only when the answer likely depends on message-level evidence.\",\n input.messages,\n input.scope,\n ),\n createSearchTool(\n \"search_episodes\",\n \"Search episode summaries only when the answer likely depends on longer-running context.\",\n input.episodes,\n input.scope,\n ),\n ];\n\n if (input.semantic) {\n tools.push(\n createSearchTool(\n \"semantic_search\",\n \"Search semantic vector evidence only when broader conceptual recall is needed.\",\n input.semantic,\n input.scope,\n ),\n );\n }\n\n return tools;\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 type { MessageSearchScope } from \"../messages/types.js\";\nimport { cosineSimilarity } from \"./embedding.js\";\nimport type { EvidenceSource } from \"./types.js\";\nimport type { VectorRecord, VectorSearchResult, VectorStore } from \"./vector-store.js\";\n\ninterface SearchRow {\n chunkId: string;\n text: string;\n chatName: string;\n senderName: string;\n sentAt: string;\n embeddingJson: string;\n}\n\nfunction parseEmbeddingJson(value: string): number[] {\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) && parsed.every((item) => typeof item === \"number\") ? parsed : [];\n } catch {\n return [];\n }\n}\n\nfunction toEvidenceSource(row: SearchRow): EvidenceSource {\n return {\n type: \"message\",\n label: row.chatName,\n sender: row.senderName,\n timestamp: row.sentAt,\n };\n}\n\nfunction buildScopeWhere(scope?: MessageSearchScope): { where: string; params: string[] } {\n const clauses: string[] = [];\n const params: string[] = [];\n if (scope?.platform) {\n clauses.push(\"m.platform = ?\");\n params.push(scope.platform);\n }\n if (scope?.platformChatId) {\n clauses.push(\"c.platform_chat_id = ?\");\n params.push(scope.platformChatId);\n }\n\n return {\n where: clauses.length > 0 ? `AND ${clauses.join(\" AND \")}` : \"\",\n params,\n };\n}\n\nexport class SqliteVectorStore implements VectorStore {\n constructor(\n private readonly database: SqliteDatabase,\n private readonly options: { model: string },\n ) {}\n\n async upsert(records: VectorRecord[]): Promise<void> {\n if (records.length === 0) {\n return;\n }\n\n const updatedAt = new Date().toISOString();\n const statement = this.database.prepare(`\n INSERT INTO message_chunk_embeddings (chunk_id, model, dimension, embedding_json, updated_at)\n VALUES (@chunkId, @model, @dimension, @embeddingJson, @updatedAt)\n ON CONFLICT(chunk_id, model)\n DO UPDATE SET\n dimension = excluded.dimension,\n embedding_json = excluded.embedding_json,\n updated_at = excluded.updated_at\n `);\n\n const transaction = this.database.transaction((input: VectorRecord[]) => {\n for (const record of input) {\n statement.run({\n chunkId: record.id,\n model: this.options.model,\n dimension: record.vector.length,\n embeddingJson: JSON.stringify(record.vector),\n updatedAt,\n });\n }\n });\n\n transaction(records);\n }\n\n async search(vector: number[], limit: number, scope?: MessageSearchScope): Promise<VectorSearchResult[]> {\n if (limit <= 0) {\n return [];\n }\n\n const scopeWhere = buildScopeWhere(scope);\n const rows = this.database\n .prepare(\n `\n SELECT\n mc.id AS chunkId,\n mc.text AS text,\n c.name AS chatName,\n m.sender_name AS senderName,\n m.sent_at AS sentAt,\n e.embedding_json AS embeddingJson\n FROM message_chunk_embeddings e\n JOIN message_chunks mc ON mc.id = e.chunk_id\n JOIN messages m ON m.id = mc.message_id\n JOIN chats c ON c.id = m.chat_id\n WHERE e.model = ?\n ${scopeWhere.where}\n `,\n )\n .all(this.options.model, ...scopeWhere.params) as SearchRow[];\n\n return rows\n .flatMap((row) => {\n const storedVector = parseEmbeddingJson(row.embeddingJson);\n if (storedVector.length === 0) {\n return [];\n }\n\n const vectorScore = cosineSimilarity(vector, storedVector);\n return {\n id: row.chunkId,\n text: row.text,\n score: vectorScore,\n vectorScore,\n source: toEvidenceSource(row),\n };\n })\n .sort((left, right) => right.vectorScore - left.vectorScore)\n .slice(0, limit);\n }\n\n count(): number {\n const row = this.database\n .prepare(\"SELECT COUNT(*) AS count FROM message_chunk_embeddings WHERE model = ?\")\n .get(this.options.model) as { count: number };\n\n return row.count;\n }\n}\n","import type { EmbeddingModel } from \"./embedding.js\";\nimport type { Retriever, RetrievalScope } 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, scope?: RetrievalScope): Promise<EvidenceBlock[]> {\n const vector = await this.embedding.embed(question);\n return this.store.search(vector, this.limit, scope);\n }\n}\n\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { EpisodeRepository } from \"../episodes/repository.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport type { MessageSearchScope } from \"../messages/types.js\";\nimport { createEmbeddingModel } from \"../llm/openai-compatible.js\";\nimport { EpisodeFtsRetriever } from \"./episode-retriever.js\";\nimport { HybridRetriever } from \"./hybrid-retriever.js\";\nimport { MessageFtsRetriever } from \"./message-retriever.js\";\nimport type { Retriever } from \"./retriever.js\";\nimport { createRagSearchTools, type RagSearchTool } from \"./search-tools.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 database: SqliteDatabase;\n messages: MessageRepository;\n excludeMessageIds?: string[];\n scope?: MessageSearchScope;\n}): Promise<{ retriever: Retriever; close: () => void }> {\n const retrievers: Retriever[] = [\n new EpisodeFtsRetriever(new EpisodeRepository(input.database)),\n new MessageFtsRetriever(input.messages, { excludeMessageIds: input.excludeMessageIds }),\n ];\n const closers: Array<() => void> = [];\n\n if (hasEmbeddingConfig(input.config, input.secrets)) {\n const vectorStore = new SqliteVectorStore(input.database, {\n model: input.config.embedding.model,\n });\n retrievers.push(new VectorRetriever(createEmbeddingModel(input.config, input.secrets), vectorStore));\n }\n\n return {\n retriever: new HybridRetriever(retrievers, { scope: input.scope }),\n close: () => {\n for (const closer of closers) {\n closer();\n }\n },\n };\n}\n\nexport async function createAgenticRagSearchTools(input: {\n config: AppConfig;\n secrets: AppSecrets;\n database: SqliteDatabase;\n messages: MessageRepository;\n excludeMessageIds?: string[];\n scope?: MessageSearchScope;\n}): Promise<{ tools: RagSearchTool[]; close: () => void }> {\n const episodes = new EpisodeFtsRetriever(new EpisodeRepository(input.database));\n const messages = new MessageFtsRetriever(input.messages, { excludeMessageIds: input.excludeMessageIds });\n const semantic = hasEmbeddingConfig(input.config, input.secrets)\n ? new VectorRetriever(\n createEmbeddingModel(input.config, input.secrets),\n new SqliteVectorStore(input.database, { model: input.config.embedding.model }),\n )\n : undefined;\n const hybrid = new HybridRetriever(semantic ? [episodes, messages, semantic] : [episodes, messages]);\n\n return {\n tools: createRagSearchTools({ hybrid, messages, episodes, semantic, scope: input.scope }),\n close: () => {},\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 type { ChatModel } from \"../rag/types.js\";\nimport type { EpisodeWindow } from \"./repository.js\";\nimport { sanitizeEpisodeSummary } from \"./sanitizer.js\";\n\nexport async function summarizeEpisodeWindow(window: EpisodeWindow, model: ChatModel, now: Date): Promise<string> {\n const transcript = window.messages\n .map((message) => `[${message.sentAt}] ${message.senderName}:${message.text}`)\n .join(\"\\n\");\n\n const summary = await model.complete([\n {\n role: \"system\",\n content:\n \"你是 ChatterCatcher 的会话记忆整理模块。你的任务是把碎片化闲聊整理成可检索事实,补全短消息、代词、缩写与上下文之间的关系。只总结明确事实,不要编造。保留重要数字、日期、链接、文件名和代码;如果图片转述里出现文件名,必须在摘要中原样保留该文件名,方便之后按文件名找回图片。如果内容像密码、API key、token 或密钥,只描述其上下文关系,不要在摘要中复写原文。消息里的“今天”“明天”“昨晚”“下周三”等相对时间表述,请基于每条消息前的发送时间戳推导为具体日期写入摘要。例如 [2026-05-05T20:00:00.000Z] 妈妈说“明天要用丝丝露”,摘要应写为“2026-05-06 要用丝丝露”。\",\n },\n {\n role: \"user\",\n content: `当前时间:${now.toISOString()}\\n群聊:${window.chatName}\\n时间:${window.startedAt} - ${window.endedAt}\\n\\n聊天记录:\\n${transcript}\\n\\n请输出一段简洁的会话记忆摘要。`,\n },\n ]);\n\n return sanitizeEpisodeSummary(summary);\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport type { ChatModel } from \"../rag/types.js\";\nimport { EpisodeRepository } from \"./repository.js\";\nimport { summarizeEpisodeWindow } from \"./summarizer.js\";\n\nexport interface ProcessEpisodesResult {\n created: number;\n}\n\nexport async function processEpisodesNow(input: {\n config: AppConfig;\n secrets: AppSecrets;\n database: SqliteDatabase;\n model: ChatModel;\n now?: Date;\n}): Promise<ProcessEpisodesResult> {\n const episodes = new EpisodeRepository(input.database);\n const created = await episodes.summarizeReadyWindows({\n now: input.now ?? new Date(),\n quietMs: input.config.episodes.quietMinutes * 60 * 1000,\n windowMs: input.config.episodes.windowMinutes * 60 * 1000,\n summarize: (window, now) => summarizeEpisodeWindow(window, input.model, now),\n });\n\n return { created: created.length };\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\n\nexport interface ResolveFeishuBotOpenIdOptions {\n fetch?: typeof fetch;\n}\n\nexport interface EnsureFeishuBotOpenIdOptions extends ResolveFeishuBotOpenIdOptions {\n onSave?: () => Promise<void>;\n}\n\nfunction getOpenApiBaseUrl(domain: AppConfig[\"feishu\"][\"domain\"]): string {\n return domain === \"lark\" ? \"https://open.larksuite.com/open-apis\" : \"https://open.feishu.cn/open-apis\";\n}\n\nasync function readFeishuJson(response: Response): Promise<unknown> {\n if (!response.ok) {\n throw new Error(`飞书接口请求失败:HTTP ${response.status}`);\n }\n\n return response.json();\n}\n\nfunction assertFeishuSuccess(payload: unknown, fallbackMessage: string): asserts payload is Record<string, unknown> {\n if (!payload || typeof payload !== \"object\") {\n throw new Error(fallbackMessage);\n }\n\n const code = (payload as { code?: unknown }).code;\n if (code !== 0) {\n const message = (payload as { msg?: unknown }).msg;\n throw new Error(typeof message === \"string\" ? message : fallbackMessage);\n }\n}\n\nexport async function resolveFeishuBotOpenId(\n config: AppConfig,\n secrets: AppSecrets,\n options: ResolveFeishuBotOpenIdOptions = {},\n): Promise<string> {\n if (!config.feishu.appId || !secrets.feishu.appSecret) {\n throw new Error(\"飞书 App ID 或 App Secret 未配置。\");\n }\n\n const fetchImpl = options.fetch ?? fetch;\n const baseUrl = getOpenApiBaseUrl(config.feishu.domain);\n const tokenPayload = await readFeishuJson(\n await fetchImpl(`${baseUrl}/auth/v3/tenant_access_token/internal`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n app_id: config.feishu.appId,\n app_secret: secrets.feishu.appSecret,\n }),\n }),\n );\n assertFeishuSuccess(tokenPayload, \"获取飞书 tenant_access_token 失败。\");\n\n const tenantAccessToken = tokenPayload.tenant_access_token;\n if (typeof tenantAccessToken !== \"string\" || !tenantAccessToken) {\n throw new Error(\"飞书 tenant_access_token 响应缺少 token。\");\n }\n\n const botInfoPayload = await readFeishuJson(\n await fetchImpl(`${baseUrl}/bot/v3/info`, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${tenantAccessToken}` },\n }),\n );\n assertFeishuSuccess(botInfoPayload, \"获取飞书机器人信息失败。\");\n\n const bot = botInfoPayload.bot;\n if (!bot || typeof bot !== \"object\") {\n throw new Error(\"飞书机器人信息响应缺少 bot。\");\n }\n\n const openId = (bot as { open_id?: unknown }).open_id;\n if (typeof openId !== \"string\" || !openId) {\n throw new Error(\"飞书机器人信息响应缺少 open_id。\");\n }\n\n return openId;\n}\n\nexport async function ensureFeishuBotOpenId(\n config: AppConfig,\n secrets: AppSecrets,\n options: EnsureFeishuBotOpenIdOptions = {},\n): Promise<string> {\n if (config.feishu.botOpenId) {\n return config.feishu.botOpenId;\n }\n\n const openId = await resolveFeishuBotOpenId(config, secrets, options);\n const previousOpenId = config.feishu.botOpenId;\n config.feishu.botOpenId = openId;\n try {\n await options.onSave?.();\n } catch (error) {\n config.feishu.botOpenId = previousOpenId;\n throw error;\n }\n return openId;\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport path from \"node:path\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport { CronJobRepository } from \"../cron/jobs.js\";\nimport { generateCronJobMessage } from \"../cron/generator.js\";\nimport type { CronJobScheduler } from \"../cron/scheduler.js\";\nimport { createCronJobScheduler } from \"../cron/scheduler.js\";\nimport { processEpisodesNow } from \"../episodes/manual-process.js\";\nimport type { GatewayIngestAndDownloadResult, GatewayIngestor } from \"../gateway/ingest.js\";\nimport type { IndexingScheduler } from \"../gateway/indexing-scheduler.js\";\nimport { createIndexingScheduler } from \"../gateway/indexing-scheduler.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { createAgenticRagSearchTools } from \"../rag/factory.js\";\nimport { processMessagesNow } from \"../rag/manual-index.js\";\nimport type { ChatModel } from \"../rag/types.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { resolveHomePath } from \"../config/paths.js\";\nimport type { FeishuReceiveMessageEvent } from \"./normalize.js\";\nimport { ImageMultimodalTaskRepository } from \"../multimodal/tasks.js\";\nimport type { MultimodalModel } from \"../multimodal/types.js\";\nimport { ImageMultimodalWorker } from \"../multimodal/worker.js\";\nimport { getFeishuQuestionDecision, isFeishuMessageAddressedToBot } from \"./question.js\";\nimport type { FeishuQuestionHandler } from \"./question.js\";\nimport { FeishuResourceDownloader } from \"./resource-downloader.js\";\nimport type { MessageSender } from \"./sender.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 episodeProcessor?: { database: SqliteDatabase; model: ChatModel; now?: () => Date };\n imageMultimodalProcessor?: { database: SqliteDatabase; model: MultimodalModel };\n indexingProcessor?: { database: SqliteDatabase };\n indexingScheduler?: IndexingScheduler;\n cronJobProcessor?: { database: SqliteDatabase; model: ChatModel; sender: Pick<MessageSender, \"sendTextToChat\" | \"sendImageToChat\"> };\n cronJobScheduler?: CronJobScheduler;\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\nfunction formatGatewayStartError(error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"PingInterval\") || message.includes(\"system busy\") || message.includes(\"1000040345\")) {\n return new Error(`飞书长连接启动失败,请检查 App ID / App Secret 是否正确;原始错误:${message}`);\n }\n\n return error instanceof Error ? error : new Error(message);\n}\n\nexport function createFeishuEventDispatcher(options: {\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 episodeProcessor?: { database: SqliteDatabase; model: ChatModel; now?: () => Date };\n imageMultimodalProcessor?: { database: SqliteDatabase; model: MultimodalModel };\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, options.config)) {\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 secrets: options.secrets,\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 (options.episodeProcessor) {\n const episodeResult = await processEpisodesNow({\n config: options.config,\n secrets: options.secrets,\n database: options.episodeProcessor.database,\n model: options.episodeProcessor.model,\n now: options.episodeProcessor.now?.(),\n });\n if (episodeResult.created > 0) {\n console.log(`飞书会话记忆已生成:${episodeResult.created}`);\n }\n }\n\n if (result.attachment?.downloaded) {\n console.log(`飞书附件已下载:${result.attachment.downloaded.storedPath}`);\n if (options.imageMultimodalProcessor && result.attachment.imageTask) {\n void new ImageMultimodalWorker({\n config: options.config,\n messages: new MessageRepository(options.imageMultimodalProcessor.database),\n tasks: new ImageMultimodalTaskRepository(options.imageMultimodalProcessor.database),\n model: options.imageMultimodalProcessor.model,\n multimodalModelName: options.config.multimodal.model,\n vectorIndexMessage: options.attachmentVectorIndexer,\n }).processPending().then((imageResult) => {\n console.log(\n `飞书图片多模态处理完成:processed=${imageResult.processed}, succeeded=${imageResult.succeeded}, skipped=${imageResult.skipped}, failed=${imageResult.failed}`,\n );\n }).catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`飞书图片多模态处理失败:${message}`);\n });\n }\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\nfunction resolveFeishuImagePath(config: AppConfig, imageFileName: string): string {\n const fileName = path.basename(imageFileName.trim());\n if (!fileName || fileName !== imageFileName.trim()) {\n throw new Error(\"图片文件名无效。\");\n }\n return path.join(resolveHomePath(config.storage.dataDir), \"files\", \"feishu\", fileName);\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 secrets: options.secrets,\n ingestor: options.ingestor,\n questionHandler: options.questionHandler,\n resourceDownloader: options.resourceDownloader,\n attachmentVectorIndexer: options.attachmentVectorIndexer,\n episodeProcessor: options.episodeProcessor,\n imageMultimodalProcessor: options.imageMultimodalProcessor,\n });\n\n const indexingScheduler = options.indexingScheduler ?? (\n options.indexingProcessor\n ? createIndexingScheduler({\n schedule: options.config.schedules.indexing,\n work: async () => {\n await processMessagesNow({\n config: options.config,\n secrets: options.secrets,\n database: options.indexingProcessor!.database,\n limit: 10_000,\n });\n },\n })\n : undefined\n );\n\n const cronJobScheduler = options.cronJobScheduler ?? (\n options.cronJobProcessor\n ? createCronJobScheduler({\n repository: new CronJobRepository(options.cronJobProcessor.database),\n sendTextToChat: (chatId, text) => options.cronJobProcessor!.sender.sendTextToChat(chatId, text),\n sendImageToChat: options.cronJobProcessor.sender.sendImageToChat\n ? (chatId, imageFileName) => options.cronJobProcessor!.sender.sendImageToChat!(\n chatId,\n resolveFeishuImagePath(options.config, imageFileName),\n )\n : undefined,\n generateMessage: async (job, now) => {\n const { tools, close } = await createAgenticRagSearchTools({\n config: options.config,\n secrets: options.secrets,\n database: options.cronJobProcessor!.database,\n messages: new MessageRepository(options.cronJobProcessor!.database),\n scope: { platform: \"feishu\", platformChatId: job.chatId },\n });\n try {\n return await generateCronJobMessage({ prompt: job.prompt, model: options.cronJobProcessor!.model, tools, now });\n } finally {\n close();\n }\n },\n })\n : undefined\n );\n\n return {\n async start() {\n try {\n await wsClient.start({ eventDispatcher });\n indexingScheduler?.start();\n cronJobScheduler?.start();\n } catch (error) {\n indexingScheduler?.stop();\n cronJobScheduler?.stop();\n throw formatGatewayStartError(error);\n }\n },\n stop() {\n indexingScheduler?.stop();\n cronJobScheduler?.stop();\n wsClient.close({ force: true });\n },\n };\n}\n","import crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { getNextCronRun, isValidCronSchedule } from \"./schedule.js\";\n\nexport type CronJobStatus = \"active\" | \"deleted\";\n\nexport interface CronJobRecord {\n id: string;\n chatId: string;\n createdByOpenId?: string;\n schedule: string;\n prompt: string;\n imageFileName?: string;\n status: CronJobStatus;\n lastRunAt?: string;\n nextRunAt: string;\n lastError?: string;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface CronJobRepositoryOptions {\n now?: () => Date;\n}\n\ninterface CronJobRow {\n id: string;\n chatId: string;\n createdByOpenId: string | null;\n schedule: string;\n prompt: string;\n imageFileName: string | null;\n status: CronJobStatus;\n lastRunAt: string | null;\n nextRunAt: string;\n lastError: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport class CronJobRepository {\n private readonly now: () => Date;\n\n constructor(\n private readonly database: SqliteDatabase,\n options: CronJobRepositoryOptions = {},\n ) {\n this.now = options.now ?? (() => new Date());\n }\n\n create(input: {\n chatId: string;\n createdByOpenId?: string;\n schedule: string;\n prompt: string;\n imageFileName?: string;\n }): CronJobRecord {\n const schedule = input.schedule.trim();\n const prompt = input.prompt.trim();\n const imageFileName = input.imageFileName?.trim();\n if (!isValidCronSchedule(schedule)) {\n throw new Error(\"cron 表达式无效。\");\n }\n if (!prompt) {\n throw new Error(\"定时任务 prompt 不能为空。\");\n }\n\n const now = this.now();\n const nextRunAt = getNextCronRun(schedule, now);\n if (!nextRunAt) {\n throw new Error(\"无法计算下一次执行时间。\");\n }\n\n const record: CronJobRecord = {\n id: crypto.randomUUID(),\n chatId: input.chatId,\n createdByOpenId: input.createdByOpenId,\n schedule,\n prompt,\n ...(imageFileName ? { imageFileName } : {}),\n status: \"active\",\n nextRunAt: nextRunAt.toISOString(),\n createdAt: now.toISOString(),\n updatedAt: now.toISOString(),\n };\n\n this.database\n .prepare(\n `\n INSERT INTO cron_jobs (\n id, chat_id, created_by_open_id, schedule, prompt, image_file_name, status,\n last_run_at, next_run_at, last_error, created_at, updated_at\n )\n VALUES (\n @id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName, @status,\n NULL, @nextRunAt, NULL, @createdAt, @updatedAt\n )\n `,\n )\n .run({\n ...record,\n imageFileName: record.imageFileName ?? null,\n });\n\n return record;\n }\n\n get(id: string): CronJobRecord | null {\n return this.listByWhere(\"WHERE id = ?\", [id], 1)[0] ?? null;\n }\n\n list(limit = 100): CronJobRecord[] {\n return this.listByWhere(\"\", [], limit);\n }\n\n listByChat(chatId: string, limit = 50): CronJobRecord[] {\n return this.listByWhere(\n \"WHERE chat_id = ? AND status = 'active'\",\n [chatId],\n limit,\n );\n }\n\n listDue(now: Date, limit = 20): CronJobRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n chat_id AS chatId,\n created_by_open_id AS createdByOpenId,\n schedule,\n prompt,\n image_file_name AS imageFileName,\n status,\n last_run_at AS lastRunAt,\n next_run_at AS nextRunAt,\n last_error AS lastError,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM cron_jobs\n WHERE status = 'active' AND next_run_at <= ?\n ORDER BY next_run_at ASC, updated_at ASC\n LIMIT ?\n `,\n )\n .all(now.toISOString(), limit) as CronJobRow[];\n\n return rows.map((row) => ({\n id: row.id,\n chatId: row.chatId,\n createdByOpenId: row.createdByOpenId ?? undefined,\n schedule: row.schedule,\n prompt: row.prompt,\n imageFileName: row.imageFileName ?? undefined,\n status: row.status,\n lastRunAt: row.lastRunAt ?? undefined,\n nextRunAt: row.nextRunAt,\n lastError: row.lastError ?? undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n }));\n }\n\n deleteByChat(id: string, chatId: string): boolean {\n const now = this.now().toISOString();\n const result = this.database\n .prepare(\n `\n UPDATE cron_jobs\n SET status = 'deleted', updated_at = @updatedAt\n WHERE id = @id AND chat_id = @chatId AND status = 'active'\n `,\n )\n .run({ id, chatId, updatedAt: now });\n return result.changes > 0;\n }\n\n markSuccess(id: string, ranAt: Date): void {\n const job = this.get(id);\n if (!job) {\n return;\n }\n\n const nextRunAt = getNextCronRun(job.schedule, ranAt);\n if (!nextRunAt) {\n throw new Error(\"无法计算下一次执行时间。\");\n }\n\n this.database\n .prepare(\n `\n UPDATE cron_jobs\n SET last_run_at = @lastRunAt, next_run_at = @nextRunAt, last_error = NULL, updated_at = @updatedAt\n WHERE id = @id AND status = 'active'\n `,\n )\n .run({\n id,\n lastRunAt: ranAt.toISOString(),\n nextRunAt: nextRunAt.toISOString(),\n updatedAt: ranAt.toISOString(),\n });\n }\n\n markFailure(id: string, error: string, failedAt: Date): void {\n const job = this.get(id);\n if (!job) {\n return;\n }\n\n const nextRunAt = getNextCronRun(job.schedule, failedAt);\n if (!nextRunAt) {\n throw new Error(\"无法计算下一次执行时间。\");\n }\n\n this.database\n .prepare(\n `\n UPDATE cron_jobs\n SET last_run_at = @lastRunAt, last_error = @lastError, next_run_at = @nextRunAt, updated_at = @updatedAt\n WHERE id = @id AND status = 'active'\n `,\n )\n .run({\n id,\n lastRunAt: failedAt.toISOString(),\n lastError: error,\n nextRunAt: nextRunAt.toISOString(),\n updatedAt: failedAt.toISOString(),\n });\n }\n\n private listByWhere(\n whereSql: string,\n params: unknown[],\n limit: number,\n ): CronJobRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n chat_id AS chatId,\n created_by_open_id AS createdByOpenId,\n schedule,\n prompt,\n image_file_name AS imageFileName,\n status,\n last_run_at AS lastRunAt,\n next_run_at AS nextRunAt,\n last_error AS lastError,\n created_at AS createdAt,\n updated_at AS updatedAt\n FROM cron_jobs\n ${whereSql}\n ORDER BY updated_at DESC\n LIMIT ?\n `,\n )\n .all(...params, limit) as CronJobRow[];\n\n return rows.map((row) => ({\n id: row.id,\n chatId: row.chatId,\n createdByOpenId: row.createdByOpenId ?? undefined,\n schedule: row.schedule,\n prompt: row.prompt,\n imageFileName: row.imageFileName ?? undefined,\n status: row.status,\n lastRunAt: row.lastRunAt ?? undefined,\n nextRunAt: row.nextRunAt,\n lastError: row.lastError ?? undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n }));\n }\n}\n","interface ParsedCronSchedule {\n minute: ParsedField;\n hour: ParsedField;\n dayOfMonth: ParsedField;\n month: ParsedField;\n dayOfWeek: ParsedField;\n}\n\ntype FieldMatcher = (value: number) => boolean;\n\ninterface ParsedField {\n wildcard: boolean;\n matches: FieldMatcher;\n}\n\nexport function isValidCronSchedule(schedule: string): boolean {\n return parseCronSchedule(schedule) !== null;\n}\n\nexport function matchesCronSchedule(schedule: string, date: Date): boolean {\n const parsed = parseCronSchedule(schedule);\n if (!parsed) {\n return false;\n }\n\n return matchesParsedSchedule(parsed, date);\n}\n\nexport function getNextCronRun(schedule: string, after: Date): Date | null {\n const parsed = parseCronSchedule(schedule);\n if (!parsed) {\n return null;\n }\n\n const candidate = new Date(after);\n candidate.setSeconds(0, 0);\n candidate.setMinutes(candidate.getMinutes() + 1);\n\n const maxMinutes = 5 * 366 * 24 * 60;\n for (let i = 0; i < maxMinutes; i += 1) {\n if (matchesParsedSchedule(parsed, candidate)) {\n return new Date(candidate);\n }\n candidate.setMinutes(candidate.getMinutes() + 1);\n }\n\n return null;\n}\n\nfunction matchesParsedSchedule(schedule: ParsedCronSchedule, date: Date): boolean {\n const dayOfMonthMatches = schedule.dayOfMonth.matches(date.getDate());\n const dayOfWeekMatches = schedule.dayOfWeek.matches(date.getDay());\n const dayMatches = schedule.dayOfMonth.wildcard || schedule.dayOfWeek.wildcard\n ? dayOfMonthMatches && dayOfWeekMatches\n : dayOfMonthMatches || dayOfWeekMatches;\n\n return (\n schedule.minute.matches(date.getMinutes()) &&\n schedule.hour.matches(date.getHours()) &&\n dayMatches &&\n schedule.month.matches(date.getMonth() + 1)\n );\n}\n\nfunction parseCronSchedule(schedule: string): ParsedCronSchedule | null {\n const fields = schedule.trim().split(/\\s+/);\n if (fields.length !== 5) {\n return null;\n }\n\n const minute = parseMinuteField(fields[0]);\n const hour = parseExactOrWildcardField(fields[1], 0, 23);\n const dayOfMonth = parseExactOrWildcardField(fields[2], 1, 31);\n const month = parseExactOrWildcardField(fields[3], 1, 12);\n const dayOfWeek = parseExactOrWildcardField(fields[4], 0, 6);\n\n if (!minute || !hour || !dayOfMonth || !month || !dayOfWeek) {\n return null;\n }\n\n return { minute, hour, dayOfMonth, month, dayOfWeek };\n}\n\nfunction parseMinuteField(field: string): ParsedField | null {\n if (field === \"*\") {\n return { wildcard: true, matches: () => true };\n }\n\n const stepMatch = /^\\*\\/(\\d+)$/.exec(field);\n if (stepMatch) {\n const step = Number(stepMatch[1]);\n if (!Number.isInteger(step) || step <= 0 || step > 59) {\n return null;\n }\n\n return { wildcard: false, matches: (value) => value % step === 0 };\n }\n\n if (field.includes(\",\")) {\n const values = field.split(\",\").map((part) => parseExactNumber(part, 0, 59));\n if (values.some((value) => value === null)) {\n return null;\n }\n\n const allowed = new Set(values as number[]);\n return { wildcard: false, matches: (value) => allowed.has(value) };\n }\n\n const exact = parseExactNumber(field, 0, 59);\n if (exact === null) {\n return null;\n }\n\n return { wildcard: false, matches: (value) => value === exact };\n}\n\nfunction parseExactOrWildcardField(field: string, min: number, max: number): ParsedField | null {\n if (field === \"*\") {\n return { wildcard: true, matches: () => true };\n }\n\n const exact = parseExactNumber(field, min, max);\n if (exact === null) {\n return null;\n }\n\n return { wildcard: false, matches: (value) => value === exact };\n}\n\nfunction parseExactNumber(field: string, min: number, max: number): number | null {\n if (!/^\\d+$/.test(field)) {\n return null;\n }\n\n const value = Number(field);\n if (!Number.isInteger(value) || value < min || value > max) {\n return null;\n }\n\n return value;\n}\n","import type { RagSearchTool } from \"../rag/search-tools.js\";\nimport type { ChatMessage, ChatModel, EvidenceBlock } from \"../rag/types.js\";\n\ninterface GenerateCronJobMessageInput {\n prompt: string;\n model: ChatModel;\n tools: RagSearchTool[];\n now: Date;\n maxModelTurns?: number;\n maxToolCalls?: number;\n}\n\nconst SYSTEM_PROMPT =\n \"你正在为飞书群生成一条定时消息。可以先调用搜索工具检索本地群聊知识库。最终输出必须是可以直接发到群里的纯文本,不要输出工具调用说明。\";\n\nfunction evidenceToText(evidence: EvidenceBlock[]): string {\n if (evidence.length === 0) {\n return \"无检索证据。\";\n }\n\n return evidence.map((item, index) => `${index + 1}. ${item.text}`).join(\"\\n\");\n}\n\nfunction toolResultContent(results: EvidenceBlock[]): string {\n return JSON.stringify(results.map((item) => ({ id: item.id, text: item.text, score: item.score, source: item.source })));\n}\n\nexport async function generateCronJobMessage(input: GenerateCronJobMessageInput): Promise<string> {\n if (!input.model.completeWithTools) {\n throw new Error(\"当前 LLM 客户端不支持工具调用。\");\n }\n\n const messages: ChatMessage[] = [\n { role: \"system\", content: SYSTEM_PROMPT },\n { role: \"user\", content: `当前时间:${input.now.toISOString()}\\n任务提示词:${input.prompt}` },\n ];\n const toolsByName = new Map(input.tools.map((tool) => [tool.name, tool]));\n const evidence: EvidenceBlock[] = [];\n const maxModelTurns = input.maxModelTurns ?? 3;\n const maxToolCalls = input.maxToolCalls ?? 6;\n let toolCallsUsed = 0;\n\n for (let turn = 0; turn < maxModelTurns; turn += 1) {\n const result = await input.model.completeWithTools(messages, input.tools);\n messages.push({ role: \"assistant\", content: result.content, toolCalls: result.toolCalls, reasoningContent: result.reasoningContent });\n\n if (result.toolCalls.length === 0) {\n break;\n }\n\n for (const call of result.toolCalls) {\n if (toolCallsUsed >= maxToolCalls) {\n return input.model.complete([\n { role: \"system\", content: SYSTEM_PROMPT },\n {\n role: \"user\",\n content: `当前时间:${input.now.toISOString()}\\n任务提示词:${input.prompt}\\n\\n证据:\\n${evidenceToText(evidence)}`,\n },\n ]);\n }\n\n toolCallsUsed += 1;\n const tool = toolsByName.get(call.name);\n if (!tool) {\n messages.push({ role: \"tool\", toolCallId: call.id, content: JSON.stringify({ error: `未知工具:${call.name}` }) });\n continue;\n }\n\n try {\n const results = await tool.execute(call.input);\n evidence.push(...results);\n messages.push({ role: \"tool\", toolCallId: call.id, content: toolResultContent(results) });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n messages.push({ role: \"tool\", toolCallId: call.id, content: JSON.stringify({ error: message }) });\n }\n }\n }\n\n return input.model.complete([\n { role: \"system\", content: SYSTEM_PROMPT },\n {\n role: \"user\",\n content: `当前时间:${input.now.toISOString()}\\n任务提示词:${input.prompt}\\n\\n证据:\\n${evidenceToText(evidence)}`,\n },\n ]);\n}\n","import type { CronJobRecord, CronJobRepository } from \"./jobs.js\";\n\nexport interface CronJobScheduler {\n start(): void;\n stop(): void;\n runDueNow(): Promise<void>;\n}\n\ninterface CreateCronJobSchedulerOptions {\n repository: Pick<CronJobRepository, \"listDue\" | \"markSuccess\" | \"markFailure\">;\n generateMessage: (job: CronJobRecord, now: Date) => Promise<string>;\n sendTextToChat: (chatId: string, text: string) => Promise<void>;\n sendImageToChat?: (chatId: string, imageFileName: string) => Promise<void>;\n now?: () => Date;\n setIntervalFn?: typeof setInterval;\n clearIntervalFn?: typeof clearInterval;\n logger?: Pick<Console, \"error\">;\n}\n\nexport function createCronJobScheduler(options: CreateCronJobSchedulerOptions): CronJobScheduler {\n const now = options.now ?? (() => new Date());\n const setIntervalFn = options.setIntervalFn ?? setInterval;\n const clearIntervalFn = options.clearIntervalFn ?? clearInterval;\n const logger = options.logger ?? console;\n let timer: ReturnType<typeof setInterval> | undefined;\n let running = false;\n\n const runDueNow = async (): Promise<void> => {\n if (running) {\n return;\n }\n\n running = true;\n const startedAt = now();\n try {\n const jobs = options.repository.listDue(startedAt);\n for (const job of jobs) {\n try {\n const text = await options.generateMessage(job, startedAt);\n await options.sendTextToChat(job.chatId, text);\n if (job.imageFileName) {\n if (!options.sendImageToChat) {\n throw new Error(\"当前定时任务运行环境不支持发送图片。\");\n }\n await options.sendImageToChat(job.chatId, job.imageFileName);\n }\n options.repository.markSuccess(job.id, startedAt);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n options.repository.markFailure(job.id, message, startedAt);\n logger.error(`CRONJob 执行失败:${job.id} ${message}`);\n }\n }\n } finally {\n running = false;\n }\n };\n\n return {\n start() {\n if (timer) {\n return;\n }\n\n void runDueNow();\n timer = setIntervalFn(() => {\n void runDueNow();\n }, 60_000);\n },\n stop() {\n if (!timer) {\n return;\n }\n\n clearIntervalFn(timer);\n timer = undefined;\n },\n runDueNow,\n };\n}\n","export interface IndexingScheduler {\n start(): void;\n stop(): void;\n runDueNow(): Promise<void>;\n}\n\ninterface CreateIndexingSchedulerOptions {\n schedule: string;\n work: () => Promise<void>;\n now?: () => Date;\n setIntervalFn?: typeof setInterval;\n clearIntervalFn?: typeof clearInterval;\n logger?: Pick<Console, \"error\" | \"warn\">;\n}\n\ninterface ParsedCronSchedule {\n minute: MinuteMatcher;\n hour: FieldMatcher;\n dayOfMonth: FieldMatcher;\n month: FieldMatcher;\n dayOfWeek: FieldMatcher;\n}\n\ntype FieldMatcher = (value: number) => boolean;\ntype MinuteMatcher = FieldMatcher;\n\nexport function matchesCronMinuteSchedule(schedule: string, date: Date): boolean {\n const parsed = parseCronSchedule(schedule);\n if (!parsed) {\n return false;\n }\n\n return (\n parsed.minute(date.getMinutes()) &&\n parsed.hour(date.getHours()) &&\n parsed.dayOfMonth(date.getDate()) &&\n parsed.month(date.getMonth() + 1) &&\n parsed.dayOfWeek(date.getDay())\n );\n}\n\nexport function createIndexingScheduler(options: CreateIndexingSchedulerOptions): IndexingScheduler {\n const now = options.now ?? (() => new Date());\n const setIntervalFn = options.setIntervalFn ?? setInterval;\n const clearIntervalFn = options.clearIntervalFn ?? clearInterval;\n const logger = options.logger ?? console;\n const parsed = parseCronSchedule(options.schedule);\n\n let timer: ReturnType<typeof setInterval> | undefined;\n let running = false;\n\n const runDueNow = async (): Promise<void> => {\n if (!parsed || running || !matchesParsedSchedule(parsed, now())) {\n return;\n }\n\n running = true;\n try {\n await options.work();\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger.error(`定时消息索引失败:${message}`);\n } finally {\n running = false;\n }\n };\n\n return {\n start() {\n if (!parsed || timer) {\n return;\n }\n\n timer = setIntervalFn(() => {\n void runDueNow();\n }, 60_000);\n },\n stop() {\n if (!timer) {\n return;\n }\n\n clearIntervalFn(timer);\n timer = undefined;\n },\n runDueNow,\n };\n}\n\nfunction matchesParsedSchedule(schedule: ParsedCronSchedule, date: Date): boolean {\n return (\n schedule.minute(date.getMinutes()) &&\n schedule.hour(date.getHours()) &&\n schedule.dayOfMonth(date.getDate()) &&\n schedule.month(date.getMonth() + 1) &&\n schedule.dayOfWeek(date.getDay())\n );\n}\n\nfunction parseCronSchedule(schedule: string): ParsedCronSchedule | null {\n const fields = schedule.trim().split(/\\s+/);\n if (fields.length !== 5) {\n return null;\n }\n\n const minute = parseMinuteField(fields[0]);\n const hour = parseExactOrWildcardField(fields[1], 0, 23);\n const dayOfMonth = parseExactOrWildcardField(fields[2], 1, 31);\n const month = parseExactOrWildcardField(fields[3], 1, 12);\n const dayOfWeek = parseExactOrWildcardField(fields[4], 0, 6);\n\n if (!minute || !hour || !dayOfMonth || !month || !dayOfWeek) {\n return null;\n }\n\n return { minute, hour, dayOfMonth, month, dayOfWeek };\n}\n\nfunction parseMinuteField(field: string): MinuteMatcher | null {\n if (field === \"*\") {\n return () => true;\n }\n\n const stepMatch = /^\\*\\/(\\d+)$/.exec(field);\n if (stepMatch) {\n const step = Number(stepMatch[1]);\n if (!Number.isInteger(step) || step <= 0 || step > 59) {\n return null;\n }\n\n return (value) => value % step === 0;\n }\n\n if (field.includes(\",\")) {\n const values = field.split(\",\").map((part) => parseExactNumber(part, 0, 59));\n if (values.some((value) => value === null)) {\n return null;\n }\n\n const allowed = new Set(values as number[]);\n return (value) => allowed.has(value);\n }\n\n const exact = parseExactNumber(field, 0, 59);\n if (exact === null) {\n return null;\n }\n\n return (value) => value === exact;\n}\n\nfunction parseExactOrWildcardField(field: string, min: number, max: number): FieldMatcher | null {\n if (field === \"*\") {\n return () => true;\n }\n\n const exact = parseExactNumber(field, min, max);\n if (exact === null) {\n return null;\n }\n\n return (value) => value === exact;\n}\n\nfunction parseExactNumber(field: string, min: number, max: number): number | null {\n if (!/^\\d+$/.test(field)) {\n return null;\n }\n\n const value = Number(field);\n if (!Number.isInteger(value) || value < min || value > max) {\n return null;\n }\n\n return value;\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 type { EmbeddingModel } from \"./embedding.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 embedding?: EmbeddingModel;\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 model: input.config.embedding.model,\n });\n const embedding = input.embedding ?? createEmbeddingModel(input.config, input.secrets);\n const stats = await indexMessageChunks({\n messages: new MessageRepository(input.database),\n embedding,\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}\n","import crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport type {\n EnqueueImageMultimodalTaskInput,\n ImageMultimodalTaskRecord,\n ImageMultimodalTaskStatus,\n} from \"./types.js\";\n\ninterface ImageMultimodalTaskRow {\n id: string;\n source_message_id: string;\n platform_message_id: string;\n image_key: string;\n stored_path: string;\n mime_type: string;\n status: ImageMultimodalTaskStatus;\n attempts: number;\n last_error: string | null;\n derived_message_id: string | null;\n created_at: string;\n updated_at: string;\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction stableId(sourceMessageId: string, imageKey: string): string {\n return crypto.createHash(\"sha256\").update(`${sourceMessageId}\u001f${imageKey}`).digest(\"hex\").slice(0, 32);\n}\n\nfunction mapRow(row: ImageMultimodalTaskRow | undefined): ImageMultimodalTaskRecord | undefined {\n if (!row) {\n return undefined;\n }\n\n return {\n id: row.id,\n sourceMessageId: row.source_message_id,\n platformMessageId: row.platform_message_id,\n imageKey: row.image_key,\n storedPath: row.stored_path,\n mimeType: row.mime_type,\n status: row.status,\n attempts: row.attempts,\n ...(row.last_error ? { lastError: row.last_error } : {}),\n ...(row.derived_message_id ? { derivedMessageId: row.derived_message_id } : {}),\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport class ImageMultimodalTaskRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n enqueue(input: EnqueueImageMultimodalTaskInput): ImageMultimodalTaskRecord {\n const id = stableId(input.sourceMessageId, input.imageKey);\n const timestamp = nowIso();\n\n this.database\n .prepare(\n `\n INSERT INTO image_multimodal_tasks (\n id,\n source_message_id,\n platform_message_id,\n image_key,\n stored_path,\n mime_type,\n status,\n attempts,\n created_at,\n updated_at\n )\n VALUES (\n @id,\n @sourceMessageId,\n @platformMessageId,\n @imageKey,\n @storedPath,\n @mimeType,\n 'pending',\n 0,\n @createdAt,\n @updatedAt\n )\n ON CONFLICT(source_message_id, image_key)\n DO UPDATE SET\n platform_message_id = excluded.platform_message_id,\n stored_path = excluded.stored_path,\n mime_type = excluded.mime_type,\n status = 'pending',\n attempts = 0,\n last_error = NULL,\n derived_message_id = NULL,\n updated_at = excluded.updated_at\n `,\n )\n .run({\n id,\n sourceMessageId: input.sourceMessageId,\n platformMessageId: input.platformMessageId,\n imageKey: input.imageKey,\n storedPath: input.storedPath,\n mimeType: input.mimeType,\n createdAt: timestamp,\n updatedAt: timestamp,\n });\n\n const record = this.getById(id);\n if (!record) {\n throw new Error(`图片多模态任务写入失败:${id}`);\n }\n\n return record;\n }\n\n listPending(limit = 10): ImageMultimodalTaskRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n source_message_id,\n platform_message_id,\n image_key,\n stored_path,\n mime_type,\n status,\n attempts,\n last_error,\n derived_message_id,\n created_at,\n updated_at\n FROM image_multimodal_tasks\n WHERE status = 'pending'\n ORDER BY updated_at ASC\n LIMIT ?\n `,\n )\n .all(limit) as ImageMultimodalTaskRow[];\n\n return rows.map((row) => mapRow(row)).filter((row): row is ImageMultimodalTaskRecord => Boolean(row));\n }\n\n markRunning(id: string): ImageMultimodalTaskRecord {\n const result = this.database\n .prepare(\n `\n UPDATE image_multimodal_tasks\n SET status = 'running',\n attempts = attempts + 1,\n last_error = NULL,\n updated_at = @updatedAt\n WHERE id = @id AND status = 'pending'\n `,\n )\n .run({ id, updatedAt: nowIso() });\n\n if (result.changes === 0) {\n throw new Error(`图片多模态任务状态无法更新:${id}`);\n }\n\n return this.requireById(id);\n }\n\n markSucceeded(id: string, derivedMessageId: string): ImageMultimodalTaskRecord {\n this.database\n .prepare(\n `\n UPDATE image_multimodal_tasks\n SET status = 'succeeded',\n last_error = NULL,\n derived_message_id = @derivedMessageId,\n updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({ id, derivedMessageId, updatedAt: nowIso() });\n\n return this.requireById(id);\n }\n\n markSkipped(id: string, reason: string): ImageMultimodalTaskRecord {\n this.database\n .prepare(\n `\n UPDATE image_multimodal_tasks\n SET status = 'skipped',\n last_error = @reason,\n derived_message_id = NULL,\n updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({ id, reason, updatedAt: nowIso() });\n\n return this.requireById(id);\n }\n\n markFailed(id: string, error: string, finalFailure: boolean): ImageMultimodalTaskRecord {\n this.database\n .prepare(\n `\n UPDATE image_multimodal_tasks\n SET status = @status,\n last_error = @error,\n derived_message_id = NULL,\n updated_at = @updatedAt\n WHERE id = @id\n `,\n )\n .run({ id, status: finalFailure ? \"failed\" : \"pending\", error, updatedAt: nowIso() });\n\n return this.requireById(id);\n }\n\n getById(id: string): ImageMultimodalTaskRecord | undefined {\n const row = this.database\n .prepare(\n `\n SELECT\n id,\n source_message_id,\n platform_message_id,\n image_key,\n stored_path,\n mime_type,\n status,\n attempts,\n last_error,\n derived_message_id,\n created_at,\n updated_at\n FROM image_multimodal_tasks\n WHERE id = ?\n `,\n )\n .get(id) as ImageMultimodalTaskRow | undefined;\n\n return mapRow(row);\n }\n\n private requireById(id: string): ImageMultimodalTaskRecord {\n const record = this.getById(id);\n if (!record) {\n throw new Error(`图片多模态任务不存在:${id}`);\n }\n return record;\n }\n}\n","import path from \"node:path\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport type { EpisodeRepository, EpisodeWindow } from \"../episodes/repository.js\";\nimport type { MessageRepository } from \"../messages/repository.js\";\nimport type { ImageMultimodalTaskRepository } from \"./tasks.js\";\nimport type { ImageMultimodalTaskRecord, MultimodalModel } from \"./types.js\";\n\nexport interface ImageMultimodalWorkerResult {\n processed: number;\n succeeded: number;\n skipped: number;\n failed: number;\n}\n\nexport interface ImageMultimodalWorkerOptions {\n config: AppConfig;\n messages: MessageRepository;\n tasks: ImageMultimodalTaskRepository;\n model: MultimodalModel;\n multimodalModelName: string;\n episodes?: EpisodeRepository;\n vectorIndexMessage?: (messageId: string) => Promise<{ chunks: number; vectors: number }>;\n summarizeEpisode?: (window: EpisodeWindow) => Promise<string>;\n}\n\nexport class ImageMultimodalWorker {\n constructor(private readonly options: ImageMultimodalWorkerOptions) {}\n\n async processPending(limit = 10): Promise<ImageMultimodalWorkerResult> {\n const result: ImageMultimodalWorkerResult = { processed: 0, succeeded: 0, skipped: 0, failed: 0 };\n const pending = this.options.tasks.listPending(limit);\n\n for (const task of pending) {\n result.processed += 1;\n await this.processTask(task, result);\n }\n\n return result;\n }\n\n private async processTask(task: ImageMultimodalTaskRecord, result: ImageMultimodalWorkerResult): Promise<void> {\n let running: ImageMultimodalTaskRecord;\n try {\n running = this.options.tasks.markRunning(task.id);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (message.startsWith(\"图片多模态任务状态无法更新:\")) {\n return;\n }\n throw error;\n }\n\n try {\n const imageFileName = path.basename(running.storedPath);\n const described = await this.options.model.describeImage({\n imagePath: running.storedPath,\n mimeType: running.mimeType,\n context: `图片文件名:${imageFileName}`,\n });\n\n if (!described.isMeaningful) {\n this.options.tasks.markSkipped(running.id, described.reason || \"多模态模型判定图片无意义。\");\n result.skipped += 1;\n return;\n }\n\n const derivedMessageId = this.options.messages.createImageSummaryMessage({\n sourceMessageId: running.sourceMessageId,\n imageKey: running.imageKey,\n imageFileName,\n summary: described.summary,\n reason: described.reason,\n multimodalModel: this.options.multimodalModelName,\n generatedAt: new Date().toISOString(),\n });\n\n if (this.options.vectorIndexMessage) {\n await this.options.vectorIndexMessage(derivedMessageId);\n }\n if (this.options.episodes && this.options.summarizeEpisode) {\n await this.options.episodes.refreshWindowForMessage({\n messageId: derivedMessageId,\n windowMs: this.options.config.episodes.windowMinutes * 60 * 1000,\n summarize: this.options.summarizeEpisode,\n });\n }\n\n this.options.tasks.markSucceeded(running.id, derivedMessageId);\n result.succeeded += 1;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.options.tasks.markFailed(running.id, message, running.attempts >= 3);\n result.failed += 1;\n }\n }\n}\n","import type { ChatTool } from \"../rag/types.js\";\nimport type { CronJobRepository } from \"./jobs.js\";\n\nexport interface CronJobTool extends ChatTool {\n execute(input: unknown): Promise<string>;\n}\n\ninterface CreateCronJobToolsInput {\n repository: CronJobRepository;\n chatId: string;\n createdByOpenId?: string;\n}\n\nfunction readString(input: unknown, key: string): string {\n const value =\n typeof input === \"object\" && input !== null && key in input\n ? (input as Record<string, unknown>)[key]\n : undefined;\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${key} 必须是非空字符串。`);\n }\n return value.trim();\n}\n\nfunction readOptionalString(input: unknown, key: string): string | undefined {\n const value =\n typeof input === \"object\" && input !== null && key in input\n ? (input as Record<string, unknown>)[key]\n : undefined;\n if (value === undefined || value === null) {\n return undefined;\n }\n if (typeof value !== \"string\") {\n throw new Error(`${key} 必须是字符串。`);\n }\n const trimmed = value.trim();\n return trimmed || undefined;\n}\n\nexport function createCronJobTools(input: CreateCronJobToolsInput): CronJobTool[] {\n return [\n {\n name: \"create_cron_job\",\n description:\n \"Create a scheduled AI message for the current Feishu chat only. The schedule must be a five-field cron string.\",\n inputSchema: {\n type: \"object\",\n properties: {\n schedule: {\n type: \"string\",\n description: \"Five-field cron schedule, for example 0 9 * * *.\",\n },\n prompt: {\n type: \"string\",\n description: \"Prompt used later to generate the scheduled message.\",\n },\n imageFileName: {\n type: \"string\",\n description: \"Optional image filename already stored from the current chat, for example om_xxx-image.jpg.\",\n },\n },\n required: [\"schedule\", \"prompt\"],\n additionalProperties: false,\n },\n execute: async (rawInput) => {\n const job = input.repository.create({\n chatId: input.chatId,\n createdByOpenId: input.createdByOpenId,\n schedule: readString(rawInput, \"schedule\"),\n prompt: readString(rawInput, \"prompt\"),\n imageFileName: readOptionalString(rawInput, \"imageFileName\"),\n });\n return JSON.stringify({ ok: true, job });\n },\n },\n {\n name: \"list_cron_jobs\",\n description: \"List active scheduled AI messages for the current Feishu chat only.\",\n inputSchema: { type: \"object\", properties: {}, additionalProperties: false },\n execute: async () => JSON.stringify({ ok: true, jobs: input.repository.listByChat(input.chatId) }),\n },\n {\n name: \"delete_cron_job\",\n description: \"Delete a scheduled AI message by ID, only if it belongs to the current Feishu chat.\",\n inputSchema: {\n type: \"object\",\n properties: {\n id: {\n type: \"string\",\n description: \"Cron job ID returned by create_cron_job or list_cron_jobs.\",\n },\n },\n required: [\"id\"],\n additionalProperties: false,\n },\n execute: async (rawInput) => {\n const id = readString(rawInput, \"id\");\n const ok = input.repository.deleteByChat(id, input.chatId);\n return JSON.stringify({\n ok,\n id,\n message: ok ? \"定时任务已删除。\" : \"没有找到当前群里的这个定时任务。\",\n });\n },\n },\n ];\n}\n","import crypto from \"node:crypto\";\nimport type { SqliteDatabase } from \"../db/database.js\";\n\nexport interface QaLogRecord {\n id: string;\n chatId: string | null;\n questionMessageId: string | null;\n question: string;\n answer: string;\n citations: unknown[];\n retrievalDebug: Record<string, unknown>;\n status: \"answered\" | \"failed\";\n error: string | null;\n createdAt: string;\n}\n\nexport interface CreateQaLogInput {\n chatId?: string;\n questionMessageId?: string;\n question: string;\n answer: string;\n citations: unknown[];\n retrievalDebug: Record<string, unknown>;\n status: \"answered\" | \"failed\";\n error?: string;\n createdAt: string;\n}\n\ninterface QaLogRow {\n id: string;\n chat_id: string | null;\n question_message_id: string | null;\n question: string;\n answer: string;\n citations_json: string;\n retrieval_debug_json: string;\n status: \"answered\" | \"failed\";\n error: string | null;\n created_at: string;\n}\n\nfunction clampLimit(limit: number): number {\n return Math.max(1, Math.min(200, Math.trunc(limit)));\n}\n\nexport class QaLogRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n create(input: CreateQaLogInput): QaLogRecord {\n const record: QaLogRecord = {\n id: `qa_${crypto.randomUUID()}`,\n chatId: input.chatId ?? null,\n questionMessageId: input.questionMessageId ?? null,\n question: input.question,\n answer: input.answer,\n citations: input.citations,\n retrievalDebug: input.retrievalDebug,\n status: input.status,\n error: input.error ?? null,\n createdAt: input.createdAt,\n };\n\n this.database\n .prepare(\n `\n INSERT INTO qa_logs (\n id,\n chat_id,\n question_message_id,\n question,\n answer,\n citations_json,\n retrieval_debug_json,\n status,\n error,\n created_at\n )\n VALUES (\n @id,\n @chatId,\n @questionMessageId,\n @question,\n @answer,\n @citationsJson,\n @retrievalDebugJson,\n @status,\n @error,\n @createdAt\n )\n `,\n )\n .run({\n id: record.id,\n chatId: record.chatId,\n questionMessageId: record.questionMessageId,\n question: record.question,\n answer: record.answer,\n citationsJson: JSON.stringify(record.citations),\n retrievalDebugJson: JSON.stringify(record.retrievalDebug),\n status: record.status,\n error: record.error,\n createdAt: record.createdAt,\n });\n\n return record;\n }\n\n listRecent(limit: number): QaLogRecord[] {\n const rows = this.database\n .prepare(\n `\n SELECT\n id,\n chat_id,\n question_message_id,\n question,\n answer,\n citations_json,\n retrieval_debug_json,\n status,\n error,\n created_at\n FROM qa_logs\n ORDER BY created_at DESC\n LIMIT ?\n `,\n )\n .all(clampLimit(limit)) as QaLogRow[];\n\n return rows.map((row) => ({\n id: row.id,\n chatId: row.chat_id,\n questionMessageId: row.question_message_id,\n question: row.question,\n answer: row.answer,\n citations: JSON.parse(row.citations_json) as unknown[],\n retrievalDebug: JSON.parse(row.retrieval_debug_json) as Record<string, unknown>,\n status: row.status,\n error: row.error,\n createdAt: row.created_at,\n }));\n }\n\n getCount(): number {\n const row = this.database.prepare(\"SELECT COUNT(*) AS count FROM qa_logs\").get() as { count: number };\n return row.count;\n }\n}\n","import type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport { CronJobRepository } from \"../cron/jobs.js\";\nimport type { CronJobTool } from \"../cron/tools.js\";\nimport { createCronJobTools } from \"../cron/tools.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { createAgenticRagSearchTools } from \"../rag/factory.js\";\nimport { QaLogRepository } from \"../rag/qa-logs.js\";\nimport type { RagSearchTool } from \"../rag/search-tools.js\";\nimport type { ChatMessage, ChatModel, ChatTool, EvidenceBlock } 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(/@/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\ntype FeishuExecutableTool = (RagSearchTool | CronJobTool) & ChatTool;\n\nconst FEISHU_TOOL_SYSTEM_PROMPT =\n `你是飞书群聊助手。你可以先搜索本地知识来回答问题;当用户明确要求创建、查看或删除群消息定时任务时,也可以调用定时任务工具。定时任务工具只管理当前群聊,不能跨群操作。若用户要求定时任务发送图片,只能使用当前群聊里已经下载入库的图片文件名,并在创建定时任务时把文件名填入 imageFileName;不要编造本地路径。若用户用自然语言描述时间,你需要先将其转换为五字段 cron 表达式(分 时 日 月 周),再调用工具。当前时间会提供给你。检索证据中的时间戳是消息被发送时的真实时间。回答时若涉及相对时间表述(如消息中说”明天””今晚”),必须基于证据中每条消息的时间戳推导为具体日期,不要照搬原文的相对表述。对于一般问答,先按需调用搜索工具,再基于工具返回的证据直接给出最终答案;若引用了检索结果,要在答案里直接写出引用内容。不要声称完成了未实际调用的操作。重要:你的回答必须是面向群成员的自然语言,绝对不能输出 JSON、工具调用细节或原始的搜索结果格式。用户只应看到你整合后的最终答案。`;\n\nconst DEFAULT_MAX_MODEL_TURNS = 4;\nconst DEFAULT_MAX_TOOL_CALLS = 8;\nconst FEISHU_TOOL_LOOP_FALLBACK = \"定时任务操作已提交,但模型没有生成最终回复。\";\nconst FEISHU_TOOL_LOOP_LIMIT_REACHED = \"工具调用次数已达到上限,请缩小请求后重试。\";\n\nfunction toToolResultContent(value: unknown): string {\n if (typeof value === \"string\") return value;\n return JSON.stringify(value);\n}\n\nfunction isEvidenceBlockArray(value: unknown): value is EvidenceBlock[] {\n return Array.isArray(value) && value.length > 0 && typeof (value[0] as EvidenceBlock)?.text === \"string\";\n}\n\nfunction formatEvidenceBlocks(blocks: EvidenceBlock[]): string {\n return blocks\n .map((block, index) => {\n const source = block.source;\n const sender = source.sender ? `${source.sender} ` : \"\";\n const timestamp = source.timestamp ? `(${source.timestamp.slice(0, 19).replace(\"T\", \" \")})` : \"\";\n const header = `[证据${index + 1}] ${sender}${timestamp}:`;\n return `${header}\\n${block.text}`;\n })\n .join(\"\\n\\n\");\n}\n\nfunction toToolErrorContent(message: string): string {\n return JSON.stringify({ ok: false, error: message });\n}\n\nasync function executeFeishuTool(tool: FeishuExecutableTool, input: unknown): Promise<string> {\n const result = await tool.execute(input);\n if (isEvidenceBlockArray(result)) {\n return formatEvidenceBlocks(result);\n }\n return toToolResultContent(result);\n}\n\nasync function runFeishuToolLoop(input: {\n question: string;\n now: Date;\n model: ChatModel;\n tools: FeishuExecutableTool[];\n maxModelTurns?: number;\n maxToolCalls?: number;\n}): Promise<string> {\n if (!input.model.completeWithTools) {\n throw new Error(\"当前 LLM 客户端不支持工具调用。\");\n }\n\n const maxModelTurns = input.maxModelTurns ?? DEFAULT_MAX_MODEL_TURNS;\n const maxToolCalls = input.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;\n const messages: ChatMessage[] = [\n { role: \"system\", content: FEISHU_TOOL_SYSTEM_PROMPT },\n { role: \"user\", content: `当前时间:${input.now.toISOString()}\\n问题:${input.question}` },\n ];\n const toolsByName = new Map(input.tools.map((tool) => [tool.name, tool]));\n let toolCallsUsed = 0;\n\n for (let turn = 0; turn < maxModelTurns; turn += 1) {\n const assistantResult = await input.model.completeWithTools(messages, input.tools);\n messages.push({\n role: \"assistant\",\n content: assistantResult.content,\n toolCalls: assistantResult.toolCalls,\n reasoningContent: assistantResult.reasoningContent,\n });\n\n if (assistantResult.toolCalls.length === 0) {\n return assistantResult.content || FEISHU_TOOL_LOOP_FALLBACK;\n }\n\n for (const toolCall of assistantResult.toolCalls) {\n if (toolCallsUsed >= maxToolCalls) {\n return FEISHU_TOOL_LOOP_LIMIT_REACHED;\n }\n\n toolCallsUsed += 1;\n const tool = toolsByName.get(toolCall.name);\n\n if (!tool) {\n messages.push({\n role: \"tool\",\n toolCallId: toolCall.id,\n content: toToolErrorContent(`未知工具:${toolCall.name}`),\n });\n continue;\n }\n\n try {\n const result = await executeFeishuTool(tool, toolCall.input);\n messages.push({\n role: \"tool\",\n toolCallId: toolCall.id,\n content: result,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n messages.push({\n role: \"tool\",\n toolCallId: toolCall.id,\n content: toToolErrorContent(message),\n });\n }\n }\n }\n\n // Salvage: try one final completion without tools to generate an answer\n try {\n const salvageAnswer = await input.model.complete([\n ...messages,\n { role: \"system\", content: \"请基于以上所有工具返回的信息,直接给出最终答案。不要再调用工具。\" },\n ]);\n return salvageAnswer || \"抱歉,回答生成失败,请稍后重试。\";\n } catch {\n return \"抱歉,回答生成失败,请稍后重试。\";\n }\n}\n\ntype FeishuMessage = NonNullable<NonNullable<FeishuReceiveMessageEvent[\"event\"]>[\"message\"]>;\ntype FeishuMention = NonNullable<FeishuMessage[\"mentions\"]>[number];\n\nfunction isMentionForBot(mention: FeishuMention, config: AppConfig): boolean {\n if (!config.feishu.botOpenId) {\n return false;\n }\n\n return mention.id?.open_id === config.feishu.botOpenId;\n}\n\nfunction getBotMentions(payload: FeishuReceiveMessageEvent, config: AppConfig) {\n const message = payload.event?.message;\n return (message?.mentions ?? []).filter((mention) => isMentionForBot(mention, config));\n}\n\nexport function isFeishuMessageAddressedToBot(payload: FeishuReceiveMessageEvent, config: AppConfig): boolean {\n const message = payload.event?.message;\n if (!message || message.message_type !== \"text\") {\n return false;\n }\n\n return getBotMentions(payload, config).length > 0;\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 = getBotMentions(payload, config);\n const text = parseTextContent(message.content);\n const hasMention = isFeishuMessageAddressedToBot(payload, config);\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 const now = new Date();\n const qaLogs = new QaLogRepository(this.options.database);\n await this.acknowledgeQuestion(decision.chatId, questionMessageId);\n\n const { tools, close } = await createAgenticRagSearchTools({\n config: this.options.config,\n secrets: this.options.secrets,\n database: this.options.database,\n messages: new MessageRepository(this.options.database),\n excludeMessageIds: options.excludeMessageIds,\n });\n\n try {\n try {\n const cronTools = createCronJobTools({\n repository: new CronJobRepository(this.options.database),\n chatId: decision.chatId,\n createdByOpenId: payload.event?.sender?.sender_id?.open_id,\n });\n const allTools: FeishuExecutableTool[] = [...tools, ...cronTools];\n const answer = await runFeishuToolLoop({\n question: decision.question,\n now,\n tools: allTools,\n model: this.options.model,\n });\n qaLogs.create({\n chatId: decision.chatId,\n questionMessageId,\n question: decision.question,\n answer,\n citations: [],\n retrievalDebug: {},\n status: \"answered\",\n createdAt: new Date().toISOString(),\n });\n await this.sendResponse(decision.chatId, questionMessageId, answer);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n qaLogs.create({\n chatId: decision.chatId,\n questionMessageId,\n question: decision.question,\n answer: `暂时无法回答:${message}`,\n citations: [],\n retrievalDebug: {},\n status: \"failed\",\n error: message,\n createdAt: new Date().toISOString(),\n });\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 fs from \"node:fs/promises\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\n\nexport interface MessageSender {\n sendTextToChat(chatId: string, text: string): Promise<void>;\n sendImageToChat?(chatId: string, imagePath: 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 image?: {\n create(payload: {\n data: {\n image_type: \"message\";\n image: Buffer;\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\nfunction extractImageKey(response: unknown): string {\n const data = response && typeof response === \"object\" ? (response as Record<string, unknown>) : {};\n const direct = data.image_key;\n if (typeof direct === \"string\" && direct.trim()) {\n return direct.trim();\n }\n\n const nested = data.data && typeof data.data === \"object\" ? (data.data as Record<string, unknown>).image_key : undefined;\n if (typeof nested === \"string\" && nested.trim()) {\n return nested.trim();\n }\n\n throw new Error(\"飞书图片上传响应缺少 image_key。\");\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 sendImageToChat(chatId: string, imagePath: string): Promise<void> {\n const imageCreate = this.client.im.v1?.image?.create;\n if (!imageCreate) {\n throw new Error(\"当前飞书 SDK 不支持图片上传接口。\");\n }\n\n const image = await fs.readFile(imagePath);\n const uploaded = await imageCreate({\n data: {\n image_type: \"message\",\n image,\n },\n });\n const imageKey = extractImageKey(uploaded);\n const payload = {\n data: {\n receive_id: chatId,\n msg_type: \"image\",\n content: JSON.stringify({ image_key: imageKey }),\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 throw new Error(\"当前飞书 SDK 不支持消息发送接口。\");\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 { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport 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\";\nimport { ImageMultimodalTaskRepository } from \"../multimodal/tasks.js\";\nimport type { ImageMultimodalTaskRecord } from \"../multimodal/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 imageTask?: ImageMultimodalTaskRecord;\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\nfunction isMultimodalReady(config: AppConfig, secrets: AppSecrets): boolean {\n return Boolean(config.multimodal.baseUrl && config.multimodal.model && secrets.multimodal.apiKey);\n}\n\nexport class GatewayIngestor {\n private readonly messages: MessageRepository;\n private readonly jobs: FileJobRepository;\n private readonly imageTasks: ImageMultimodalTaskRepository;\n\n constructor(database: SqliteDatabase) {\n this.messages = new MessageRepository(database);\n this.jobs = new FileJobRepository(database);\n this.imageTasks = new ImageMultimodalTaskRepository(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: AppConfig;\n secrets: AppSecrets;\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 (attachment.kind === \"image\") {\n const imageTask = isMultimodalReady(input.config, input.secrets)\n ? this.imageTasks.enqueue({\n sourceMessageId: result.messageId,\n platformMessageId: result.message.platformMessageId,\n imageKey: attachment.fileKey,\n storedPath: downloaded.storedPath,\n mimeType: attachment.mimeType || \"image/jpeg\",\n })\n : undefined;\n\n return {\n ...result,\n attachment: {\n downloaded,\n ...(imageTask ? { imageTask } : {}),\n skippedReason: imageTask ? \"图片已下载,等待多模态后台处理。\" : \"图片已下载,但多模态未配置。\",\n },\n };\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, type ChildProcess } 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\nconst START_FAILURE_GRACE_MS = 250;\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\nfunction describeImmediateChildFailure(event: { type: \"error\"; error: Error } | { type: \"exit\"; code: number | null; signal: NodeJS.Signals | null }): string {\n if (event.type === \"error\") {\n return event.error.message;\n }\n\n return event.signal ? `signal=${event.signal}` : `exitCode=${event.code ?? \"unknown\"}`;\n}\n\nfunction waitForImmediateChildFailure(\n child: ChildProcess,\n graceMs = START_FAILURE_GRACE_MS,\n): Promise<{ type: \"error\"; error: Error } | { type: \"exit\"; code: number | null; signal: NodeJS.Signals | null } | null> {\n return new Promise((resolve) => {\n let settled = false;\n let timer: NodeJS.Timeout;\n\n const cleanup = () => {\n clearTimeout(timer);\n child.off(\"error\", onError);\n child.off(\"exit\", onExit);\n };\n const settle = (result: { type: \"error\"; error: Error } | { type: \"exit\"; code: number | null; signal: NodeJS.Signals | null } | null) => {\n if (settled) {\n return;\n }\n settled = true;\n cleanup();\n resolve(result);\n };\n const onError = (error: Error) => settle({ type: \"error\", error });\n const onExit = (code: number | null, signal: NodeJS.Signals | null) => settle({ type: \"exit\", code, signal });\n\n child.once(\"error\", onError);\n child.once(\"exit\", onExit);\n timer = setTimeout(() => settle(null), graceMs);\n });\n}\n\nexport async function startDetachedGateway(input: DetachedGatewayStartInput): Promise<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 const immediateFailure = await waitForImmediateChildFailure(child);\n closeStdio();\n\n if (immediateFailure) {\n return {\n started: false,\n message: `飞书 Gateway 启动失败:${describeImmediateChildFailure(immediateFailure)}。请查看日志:${logFile}`,\n pid: child.pid,\n logFile,\n };\n }\n\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 fs from \"node:fs/promises\";\nimport type { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { DescribeImageInput, DescribeImageResult, MultimodalModel } from \"./types.js\";\n\nexport interface OpenAICompatibleMultimodalOptions {\n baseUrl: string;\n apiKey: string;\n model: string;\n temperature?: number;\n}\n\ninterface MultimodalCompletionResponse {\n choices?: Array<{\n message?: {\n content?: string;\n };\n }>;\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.replace(/\\/+$/, \"\");\n}\n\nfunction buildPrompt(context?: string): string {\n const contextText = context?.trim();\n return [\n \"请理解这张图片,判断它是否包含值得进入知识库和会话记忆的有意义信息。\",\n \"请只输出 JSON,格式为 {\\\"summary\\\": string, \\\"isMeaningful\\\": boolean, \\\"reason\\\": string}。\",\n \"summary 使用简洁中文转述图片中的关键信息;无意义图片也要给出简短 summary。\",\n \"如果上下文提供了图片文件名,summary 必须原样包含该文件名,便于之后按文件名检索和发送图片。\",\n contextText ? `上下文:${contextText}` : undefined,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nfunction parseDescribeImageResult(content: string): DescribeImageResult {\n let data: unknown;\n try {\n data = JSON.parse(content);\n } catch {\n throw new Error(\"多模态模型返回的 JSON 无法解析。\");\n }\n\n if (!data || typeof data !== \"object\") {\n throw new Error(\"多模态模型返回格式不正确。\");\n }\n\n const result = data as Record<string, unknown>;\n const summary = typeof result.summary === \"string\" ? result.summary.trim() : \"\";\n if (!summary) {\n throw new Error(\"多模态模型返回的 summary 为空。\");\n }\n if (typeof result.isMeaningful !== \"boolean\") {\n throw new Error(\"多模态模型返回的 isMeaningful 不是布尔值。\");\n }\n\n const reason = typeof result.reason === \"string\" ? result.reason.trim() : \"\";\n return {\n summary,\n isMeaningful: result.isMeaningful,\n ...(reason ? { reason } : {}),\n };\n}\n\nexport class OpenAICompatibleMultimodalModel implements MultimodalModel {\n constructor(private readonly options: OpenAICompatibleMultimodalOptions) {}\n\n async describeImage(input: DescribeImageInput): Promise<DescribeImageResult> {\n if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {\n throw new Error(\"多模态配置不完整。请运行 chattercatcher setup 或 chattercatcher settings。\");\n }\n\n const image = await fs.readFile(input.imagePath);\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 {\n role: \"user\",\n content: [\n { type: \"text\", text: buildPrompt(input.context) },\n { type: \"image_url\", image_url: { url: `data:${input.mimeType};base64,${image.toString(\"base64\")}` } },\n ],\n },\n ],\n response_format: { type: \"json_object\" },\n temperature: this.options.temperature ?? 0.2,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`多模态请求失败:${response.status} ${body}`);\n }\n\n const data = (await response.json()) as MultimodalCompletionResponse;\n const content = data.choices?.[0]?.message?.content?.trim();\n if (!content) {\n throw new Error(\"多模态模型返回为空。\");\n }\n\n return parseDescribeImageResult(content);\n }\n}\n\nexport function createMultimodalModel(config: AppConfig, secrets: AppSecrets): OpenAICompatibleMultimodalModel {\n return new OpenAICompatibleMultimodalModel({\n baseUrl: config.multimodal.baseUrl,\n apiKey: secrets.multimodal.apiKey,\n model: config.multimodal.model,\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 BuildEvidencePromptInput {\n question: string;\n evidence: EvidenceBlock[];\n now: Date;\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 input: BuildEvidencePromptInput,\n options: BuildEvidencePromptOptions = {},\n): EvidencePrompt {\n const { question, evidence, now } = input;\n\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] 这样的来源标记。证据不足时说不知道,不要猜。若证据互相矛盾,优先采用时间更新且表述明确的证据;如果较新的证据只是讨论、猜测或不确定表达,不要把它当作确定更新。检索证据中的时间戳是消息被发送时的真实时间。回答时若涉及相对时间表述(如消息中说“明天”“今晚”),必须基于证据中每条消息的时间戳推导为具体日期(如“2026-05-06”),不要照搬原文的相对表述。证据中每条消息标注了发送时间。回答时优先输出绝对日期,不确定时引用原文时间戳,不要使用“今天”“明天”等依赖当前上下文的模糊表述。\",\n },\n {\n role: \"user\",\n content: `当前时间:${now.toISOString()}\\n问题:${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 now: Date;\n}): Promise<GroundedAnswer> {\n const prompt = buildEvidencePrompt({ question: input.question, evidence: input.evidence, now: input.now });\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 now?: Date;\n}\n\nexport async function askWithRag(input: AskWithRagInput): Promise<GroundedAnswer> {\n const now = input.now ?? new Date();\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 now,\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 { 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 crypto from \"node:crypto\";\nimport Fastify, { type FastifyInstance } from \"fastify\";\nimport { loadSecrets, saveSecrets } from \"../config/store.js\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport { CronJobRepository } from \"../cron/jobs.js\";\nimport { openDatabase } from \"../db/database.js\";\nimport { FileJobRepository } from \"../files/jobs.js\";\nimport { EpisodeRepository } from \"../episodes/repository.js\";\nimport { getGatewayStatus } from \"../gateway/index.js\";\nimport { MessageRepository } from \"../messages/repository.js\";\nimport { processMessagesNow } from \"../rag/manual-index.js\";\nimport { QaLogRepository } from \"../rag/qa-logs.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 <section>\n <h2>会话记忆</h2>\n <div id=\"episodes\" class=\"empty\">正在读取...</div>\n </section>\n <section>\n <h2>问答日志</h2>\n <div id=\"qa-logs\" 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 <div id=\"cron-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 episodes = document.querySelector(\"#episodes\");\n const chats = document.querySelector(\"#chats\");\n const files = document.querySelector(\"#files\");\n const fileJobs = document.querySelector(\"#file-jobs\");\n const cronJobs = document.querySelector(\"#cron-jobs\");\n const qaLogs = document.querySelector(\"#qa-logs\");\n const processMessages = document.querySelector(\"#process-messages\");\n const actionStatus = document.querySelector(\"#action-status\");\n\n let webActionToken = \"__WEB_ACTION_TOKEN__\";\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.version || \"unknown\", \"当前运行版本\", \"\"],\n [\"群聊\", status.data.chats, \"本地群聊数\", \"\"],\n [\"消息\", status.data.messages, \"已入库消息\", \"\"],\n [\"会话记忆\", status.data.episodes, \"已生成摘要\", \"\"],\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 renderEpisodes(items) {\n if (items.length === 0) {\n episodes.className = \"empty\";\n episodes.textContent = \"还没有会话记忆。默认在 10 分钟窗口静默 2 分钟后生成,也可以运行 chattercatcher process episodes 手动触发。\";\n return;\n }\n episodes.className = \"\";\n episodes.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.startedAt))} - \\${escapeHtml(formatDateTime(item.endedAt))}</span>\n <span>\\${escapeHtml(displayChatName(item.chatName, \"feishu\"))}</span>\n <span>\\${escapeHtml(item.messageCount)} 条消息</span>\n </div>\n <div class=\"message-body\">\\${escapeHtml(item.summary)}</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 function renderCronJobs(items) {\n if (items.length === 0) {\n cronJobs.className = \"empty\";\n cronJobs.textContent = \"还没有定时任务。可在飞书群里 @ 机器人创建。\";\n return;\n }\n cronJobs.className = \"\";\n cronJobs.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.schedule)}</div>\n <div class=\"message\" title=\"\\${escapeHtml(item.prompt)}\">\\${escapeHtml(item.prompt)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.id)}\">ID: \\${escapeHtml(item.id)}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.chatId)}\">群: \\${escapeHtml(item.chatId)}</div>\n <div class=\"path\">下次: \\${escapeHtml(formatDateTime(item.nextRunAt))}</div>\n <div class=\"path\" title=\"\\${escapeHtml(item.lastError || \"\")}\">\\${escapeHtml(item.lastError || \"\")}</div>\n \\${item.status === \"active\" ? \\`<button type=\"button\" data-delete-cron-job=\"\\${escapeHtml(item.id)}\">删除</button>\\` : \"\"}\n </td>\n <td>\\${escapeHtml(item.status)}</td>\n </tr>\n \\`).join(\"\")}\n </tbody>\n </table>\n \\`;\n }\n\n function renderQaLogs(items) {\n if (items.length === 0) {\n qaLogs.className = \"empty\";\n qaLogs.textContent = \"还没有问答日志。\";\n return;\n }\n qaLogs.className = \"\";\n const rows = items.map((item) => {\n const citationCount = Array.isArray(item.citations) ? item.citations.length : 0;\n return [\n '<article class=\"message-item\">',\n ' <div class=\"message-meta\">',\n \" <span>\" + escapeHtml(formatDateTime(item.createdAt)) + \"</span>\",\n \" <span>\" + escapeHtml(item.status) + \"</span>\",\n \" <span>\" + escapeHtml(citationCount) + \" 条引用</span>\",\n \" </div>\",\n \" <div class=\\\\\\\"message-body\\\\\\\"><strong>问:</strong>\" + escapeHtml(item.question) + \"</div>\",\n \" <div class=\\\\\\\"message-body\\\\\\\"><strong>答:</strong>\" + escapeHtml(item.answer) + \"</div>\",\n \"</article>\",\n ].join(\"\\\\n\");\n });\n qaLogs.innerHTML = [\n '<div class=\"message-list\">',\n rows.join(\"\"),\n \"</div>\",\n ].join(\"\\\\n\");\n }\n\n async function fetchJson(path) {\n const response = await fetch(path);\n if (!response.ok) {\n const body = await response.text();\n throw new Error(path + \" \" + response.status + \" \" + body);\n }\n return response.json();\n }\n\n function renderLoadError(element, error) {\n element.className = \"empty\";\n element.textContent = \"加载失败:\" + (error instanceof Error ? error.message : String(error));\n }\n\n async function loadSection(path, element, render) {\n try {\n render(await fetchJson(path));\n } catch (error) {\n renderLoadError(element, error);\n }\n }\n\n async function load() {\n await Promise.all([\n loadSection(\"/api/status\", metrics, renderMetrics),\n loadSection(\"/api/messages/recent?limit=20\", messages, (data) => renderMessages(data.items)),\n loadSection(\"/api/episodes?limit=10\", episodes, (data) => renderEpisodes(data.items)),\n loadSection(\"/api/chats\", chats, (data) => renderChats(data.items)),\n loadSection(\"/api/files\", files, (data) => renderFiles(data.items)),\n loadSection(\"/api/file-jobs\", fileJobs, (data) => renderFileJobs(data.items)),\n loadSection(\"/api/qa-logs?limit=10\", qaLogs, (data) => renderQaLogs(data.items)),\n loadSection(\"/api/cron-jobs\", cronJobs, (data) => renderCronJobs(data.items)),\n ]);\n }\n\n async function processNow() {\n processMessages.disabled = true;\n actionStatus.textContent = \"正在处理消息索引...\";\n try {\n const response = await fetch(\"/api/process/messages\", {\n method: \"POST\",\n headers: { \"x-chattercatcher-web-token\": webActionToken },\n });\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 document.addEventListener(\"click\", async (event) => {\n const target = event.target;\n if (!(target instanceof HTMLElement)) return;\n const id = target.dataset.deleteCronJob;\n if (!id) return;\n target.setAttribute(\"disabled\", \"disabled\");\n actionStatus.textContent = \"正在删除定时任务...\";\n try {\n const response = await fetch(\\`/api/cron-jobs/\\${encodeURIComponent(id)}\\`, {\n method: \"DELETE\",\n headers: { \"x-chattercatcher-web-token\": webActionToken },\n });\n const result = await response.json();\n actionStatus.textContent = result.ok ? \"定时任务已删除。\" : result.message || \"删除失败。\";\n await load();\n } catch (error) {\n actionStatus.textContent = error instanceof Error ? error.message : String(error);\n } finally {\n target.removeAttribute(\"disabled\");\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\nexport interface WebAppOptions {\n version?: string;\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\nfunction getWebActionToken(secrets: Awaited<ReturnType<typeof loadSecrets>>): string {\n return secrets.web.actionToken;\n}\n\nfunction readHeader(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction isAuthorizedWebAction(request: { headers: Record<string, string | string[] | undefined> }, token: string): boolean {\n const provided = readHeader(request.headers[\"x-chattercatcher-web-token\"]);\n return provided === token;\n}\n\n\nexport function createWebApp(config: AppConfig, options: WebAppOptions = {}): FastifyInstance {\n const app = Fastify({ logger: false });\n const database = openDatabase(config);\n const version = options.version ?? \"unknown\";\n const messages = new MessageRepository(database);\n const episodes = new EpisodeRepository(database);\n const fileJobs = new FileJobRepository(database);\n const qaLogs = new QaLogRepository(database);\n const cronJobs = new CronJobRepository(database);\n let webActionToken = \"\";\n const tokenReady = (async () => {\n const secrets = await loadSecrets();\n if (!secrets.web.actionToken) {\n secrets.web.actionToken = crypto.randomBytes(32).toString(\"hex\");\n await saveSecrets(secrets);\n }\n webActionToken = getWebActionToken(secrets);\n })();\n\n app.addHook(\"onClose\", async () => {\n database.close();\n });\n\n app.get(\"/api/status\", async () => {\n await tokenReady;\n return {\n app: \"ChatterCatcher\",\n version,\n gateway: getGatewayStatus(config),\n data: {\n chats: messages.getChatCount(),\n messages: messages.getMessageCount(),\n episodes: episodes.getEpisodeCount(),\n files: messages.listFiles(1_000).length,\n qaLogs: qaLogs.getCount(),\n cronJobs: cronJobs.list(1_000).length,\n },\n rag: {\n mode: \"required\",\n note: \"问答必须先检索证据,禁止全量上下文堆叠。\",\n retrieval: {\n keyword: \"SQLite FTS5\",\n vector: \"SQLite embedding\",\n hybrid: true,\n },\n },\n web: config.web,\n };\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.get(\"/api/episodes\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 20, 100);\n return {\n items: episodes.listRecentEpisodes(limit),\n };\n });\n\n app.get(\"/api/qa-logs\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 20, 100);\n return {\n items: qaLogs.listRecent(limit),\n };\n });\n\n app.get(\"/api/cron-jobs\", async (request) => {\n const limit = parseLimit((request.query as { limit?: string }).limit, 50, 200);\n return {\n items: cronJobs.list(limit),\n };\n });\n\n app.delete(\"/api/cron-jobs/:id\", async (request, reply) => {\n await tokenReady;\n if (!isAuthorizedWebAction(request, webActionToken)) {\n reply.code(403);\n return { ok: false, message: \"Web 操作未授权。\" };\n }\n\n const id = (request.params as { id: string }).id;\n const job = cronJobs.get(id);\n if (!job) {\n reply.code(404);\n return { ok: false, message: \"没有找到定时任务。\" };\n }\n\n const ok = cronJobs.deleteByChat(id, job.chatId);\n return { ok };\n });\n\n app.post(\"/api/process/messages\", async (request, reply) => {\n await tokenReady;\n if (!isAuthorizedWebAction(request, webActionToken)) {\n reply.code(403);\n return { status: \"failed\", message: \"Web 操作未授权。\" };\n }\n\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 await tokenReady;\n reply.type(\"text/html; charset=utf-8\");\n return buildHtml().replaceAll(\"__WEB_ACTION_TOKEN__\", webActionToken);\n });\n\n return app;\n}\n\nexport async function startWebServer(config: AppConfig, options: WebAppOptions = {}): Promise<void> {\n const app = createWebApp(config, options);\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 const versionText = options.version ? ` ${options.version}` : \"\";\n console.log(`ChatterCatcher Web UI${versionText}: ${url}`);\n}\n"],"mappings":"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AA;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;AA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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","fs","path","path","fs","files","input","path","fs","data","crypto","nowIso","crypto","index","input","crypto","nowIso","stableId","crypto","escapeFtsQuery","buildScopeWhere","input","index","input","input","index","toEvidenceSource","buildScopeWhere","input","input","fs","fs","path","path","input","fs","fs","path","data","input","input","lark","path","crypto","input","crypto","index","input","parseCronSchedule","matchesParsedSchedule","parseMinuteField","parseExactOrWildcardField","parseExactNumber","input","index","toEvidenceSource","input","crypto","nowIso","stableId","input","path","input","crypto","input","index","input","fs","data","data","path","lark","fs","path","input","path","fs","crypto","fs","path","fs","path","path","crypto","input","fs","asObject","fileKey","input","fs","path","input","fs","path","fs","normalizeBaseUrl","data","input","input","index","input","input","index","crypto","crypto","files","fs"]}