chattercatcher 0.1.31 → 0.2.1
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 +850 -476
- package/dist/cli.js.map +1 -1
- package/dist/index.js +847 -473
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/schema.ts","../src/config/store.ts","../src/config/paths.ts","../src/config/update.ts","../src/cron/generator.ts","../src/cron/jobs.ts","../src/cron/schedule.ts","../src/cron/scheduler.ts","../src/cron/tools.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/gateway.ts","../src/gateway/indexing-scheduler.ts","../src/rag/indexer.ts","../src/rag/manual-index.ts","../src/multimodal/tasks.ts","../src/multimodal/worker.ts","../src/feishu/members.ts","../src/rag/qa-logs.ts","../src/feishu/question.ts","../src/feishu/sender.ts","../src/feishu/markdown-post.ts","../src/feishu/normalize.ts","../src/feishu/resource-downloader.ts","../src/files/ingest.ts","../src/files/parser.ts","../src/gateway/ingest.ts","../src/rag/answer.ts","../src/rag/citations.ts","../src/rag/qa-service.ts","../src/rag/vector-store.ts","../src/web/server.ts"],"sourcesContent":["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 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\";\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 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 memberPrompt?: string;\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 systemPrompt = input.memberPrompt\n ? `${SYSTEM_PROMPT}\\n\\n${input.memberPrompt}\\n生成消息时遇到上述 ID 时优先使用对应群昵称;没有映射时保留原 ID,不要编造昵称。`\n : SYSTEM_PROMPT;\n const messages: ChatMessage[] = [\n { role: \"system\", content: systemPrompt },\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: systemPrompt },\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 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 mentionTargetName?: string;\n mentionOpenId?: string;\n mentionUserId?: 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 mentionTargetName: string | null;\n mentionOpenId: string | null;\n mentionUserId: 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 mentionTargetName?: string;\n mentionOpenId?: string;\n mentionUserId?: string;\n }): CronJobRecord {\n const schedule = input.schedule.trim();\n const prompt = input.prompt.trim();\n const imageFileName = input.imageFileName?.trim();\n const mentionTargetName = input.mentionTargetName?.trim();\n const mentionOpenId = input.mentionOpenId?.trim();\n const mentionUserId = input.mentionUserId?.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 ...(mentionTargetName ? { mentionTargetName } : {}),\n ...(mentionOpenId ? { mentionOpenId } : {}),\n ...(mentionUserId ? { mentionUserId } : {}),\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,\n mention_target_name, mention_open_id, mention_user_id, status,\n last_run_at, next_run_at, last_error, created_at, updated_at\n )\n VALUES (\n @id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName,\n @mentionTargetName, @mentionOpenId, @mentionUserId, @status,\n NULL, @nextRunAt, NULL, @createdAt, @updatedAt\n )\n `,\n )\n .run({\n ...record,\n imageFileName: record.imageFileName ?? null,\n mentionTargetName: record.mentionTargetName ?? null,\n mentionOpenId: record.mentionOpenId ?? null,\n mentionUserId: record.mentionUserId ?? 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 mention_target_name AS mentionTargetName,\n mention_open_id AS mentionOpenId,\n mention_user_id AS mentionUserId,\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 mentionTargetName: row.mentionTargetName ?? undefined,\n mentionOpenId: row.mentionOpenId ?? undefined,\n mentionUserId: row.mentionUserId ?? 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 mention_target_name AS mentionTargetName,\n mention_open_id AS mentionOpenId,\n mention_user_id AS mentionUserId,\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 mentionTargetName: row.mentionTargetName ?? undefined,\n mentionOpenId: row.mentionOpenId ?? undefined,\n mentionUserId: row.mentionUserId ?? 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 { SendTextOptions } from \"../feishu/sender.js\";\nimport 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, options?: SendTextOptions) => 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 const sendOptions = job.mentionOpenId && job.mentionTargetName\n ? { mentions: [{ openId: job.mentionOpenId, name: job.mentionTargetName }] }\n : undefined;\n if (sendOptions) {\n await options.sendTextToChat(job.chatId, text, sendOptions);\n } else {\n await options.sendTextToChat(job.chatId, text);\n }\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","import type { FeishuMemberResolver } from \"../feishu/members.js\";\nimport 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 memberResolver?: Pick<FeishuMemberResolver, \"resolveUniqueName\">;\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 mentionTargetName: {\n type: \"string\",\n description: \"Optional exact Feishu chat nickname to @ when the scheduled message is sent.\",\n },\n },\n required: [\"schedule\", \"prompt\"],\n additionalProperties: false,\n },\n execute: async (rawInput) => {\n const mentionTargetName = readOptionalString(rawInput, \"mentionTargetName\");\n const mentionTarget = mentionTargetName && input.memberResolver\n ? await input.memberResolver.resolveUniqueName(input.chatId, mentionTargetName)\n : null;\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 mentionTargetName,\n mentionOpenId: mentionTarget?.openId,\n mentionUserId: mentionTarget?.userId,\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 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 feishu_chat_members (\n chat_id TEXT NOT NULL,\n open_id TEXT NOT NULL,\n user_id TEXT,\n user_name TEXT,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (chat_id, open_id)\n );\n\n CREATE INDEX IF NOT EXISTS feishu_chat_members_chat_name_idx\n ON feishu_chat_members(chat_id, user_name);\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 CREATE TABLE IF NOT EXISTS feishu_chat_members (\n chat_id TEXT NOT NULL,\n open_id TEXT NOT NULL,\n user_id TEXT,\n user_name TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (chat_id, open_id)\n );\n\n CREATE INDEX IF NOT EXISTS feishu_chat_members_chat_name_idx\n ON feishu_chat_members(chat_id, user_name);\n `);\n\n const cronJobColumns = database.prepare(\"PRAGMA table_info(cron_jobs)\").all() as Array<{ name: string }>;\n const ensureCronJobColumn = (name: string, definition: string): void => {\n if (!cronJobColumns.some((column) => column.name === name)) {\n database.prepare(`ALTER TABLE cron_jobs ADD COLUMN ${definition}`).run();\n }\n };\n\n ensureCronJobColumn(\"image_file_name\", \"image_file_name TEXT\");\n ensureCronJobColumn(\"mention_target_name\", \"mention_target_name TEXT\");\n ensureCronJobColumn(\"mention_open_id\", \"mention_open_id TEXT\");\n ensureCronJobColumn(\"mention_user_id\", \"mention_user_id TEXT\");\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\nconst OPENAI_EMBEDDING_BATCH_SIZE = 64;\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 parseToolCallArguments(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return {};\n }\n}\n\nfunction decodeDsmlValue(value: string, isString: boolean): unknown {\n const trimmed = value.trim();\n if (isString) {\n return trimmed;\n }\n\n if (trimmed === \"true\") return true;\n if (trimmed === \"false\") return false;\n if (trimmed === \"null\") return null;\n\n const numberValue = Number(trimmed);\n if (trimmed && Number.isFinite(numberValue)) {\n return numberValue;\n }\n\n return trimmed;\n}\n\nfunction parseDsmlToolCalls(content: string | undefined): ToolCall[] {\n if (!content?.includes(\"DSML\")) {\n return [];\n }\n\n const toolCalls: ToolCall[] = [];\n const invokePattern = /<||DSML||invoke\\s+name=\"([^\"]+)\"\\s*>([\\s\\S]*?)<\\/||DSML||invoke>/g;\n const parameterPattern = /<||DSML||parameter\\s+name=\"([^\"]+)\"\\s+string=\"(true|false)\"\\s*>([\\s\\S]*?)<\\/||DSML||parameter>/g;\n\n for (const invoke of content.matchAll(invokePattern)) {\n const name = invoke[1];\n if (!name) {\n continue;\n }\n\n const input: Record<string, unknown> = {};\n const body = invoke[2] ?? \"\";\n for (const parameter of body.matchAll(parameterPattern)) {\n const parameterName = parameter[1];\n if (!parameterName) {\n continue;\n }\n input[parameterName] = decodeDsmlValue(parameter[3] ?? \"\", parameter[2] === \"true\");\n }\n\n toolCalls.push({\n id: `dsml_${toolCalls.length + 1}`,\n name,\n input,\n });\n }\n\n return toolCalls;\n}\n\nfunction parseToolCalls(message?: OpenAICompatibleMessage): ToolCall[] {\n const standardToolCalls =\n message?.tool_calls?.map((toolCall) => ({\n id: toolCall.id,\n name: toolCall.function.name,\n input: parseToolCallArguments(toolCall.function.arguments),\n })) ?? [];\n\n return standardToolCalls.length > 0 ? standardToolCalls : parseDsmlToolCalls(message?.content);\n}\n\nfunction isDsmlToolCallContent(content: string | undefined): boolean {\n return parseDsmlToolCalls(content).length > 0;\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 const toolCalls = parseToolCalls(message);\n\n return {\n content: toolCalls.length > 0 && isDsmlToolCallContent(message?.content) ? \"\" : (message?.content ?? \"\"),\n toolCalls,\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 vectors: number[][] = [];\n for (let index = 0; index < texts.length; index += OPENAI_EMBEDDING_BATCH_SIZE) {\n vectors.push(...(await this.fetchEmbeddingBatch(texts.slice(index, index + OPENAI_EMBEDDING_BATCH_SIZE))));\n }\n return vectors;\n }\n\n private async fetchEmbeddingBatch(texts: string[]): Promise<number[][]> {\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 * 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 { createFeishuChatMembersClient, FeishuMemberRepository, FeishuMemberResolver, formatFeishuMemberPrompt } from \"./members.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 memberResolver?: FeishuMemberResolver;\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 let result: GatewayIngestAndDownloadResult;\n if (options.resourceDownloader) {\n result = await options.ingestor.ingestFeishuEventAndDownloadAttachments({\n payload,\n downloader: options.resourceDownloader,\n config: options.config,\n secrets: options.secrets,\n vectorIndexMessage: options.attachmentVectorIndexer,\n memberResolver: options.memberResolver,\n });\n } else if (options.memberResolver) {\n result = await options.ingestor.ingestFeishuEventWithMembers({\n payload,\n memberResolver: options.memberResolver,\n });\n } else {\n result = options.ingestor.ingestFeishuEvent(payload);\n }\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 memberResolver = new FeishuMemberResolver({\n repository: new FeishuMemberRepository(options.ingestor.database),\n client: createFeishuChatMembersClient(new lark.Client({\n appId: options.config.feishu.appId,\n appSecret: options.secrets.feishu.appSecret,\n domain: mapDomain(options.config.feishu.domain),\n }) as Parameters<typeof createFeishuChatMembersClient>[0]),\n });\n options.questionHandler?.setMemberResolver?.(memberResolver);\n\n const eventDispatcher = createFeishuEventDispatcher({\n config: options.config,\n secrets: options.secrets,\n ingestor: options.ingestor,\n memberResolver,\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, sendOptions) => options.cronJobProcessor!.sender.sendTextToChat(chatId, text, sendOptions),\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 const memberPrompt = formatFeishuMemberPrompt(\n new FeishuMemberRepository(options.cronJobProcessor!.database).listByChat(job.chatId),\n );\n return await generateCronJobMessage({\n prompt: job.prompt,\n model: options.cronJobProcessor!.model,\n tools,\n now,\n memberPrompt,\n });\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","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\nconst EMBEDDING_INDEX_BATCH_SIZE = 64;\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: number[][] = [];\n for (let index = 0; index < chunks.length; index += EMBEDDING_INDEX_BATCH_SIZE) {\n vectors.push(\n ...(await input.embedding.embedBatch(\n chunks.slice(index, index + EMBEDDING_INDEX_BATCH_SIZE).map((chunk) => chunk.text),\n )),\n );\n }\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 { SqliteDatabase } from \"../db/database.js\";\n\nexport interface FeishuChatMemberRecord {\n chatId: string;\n openId: string;\n userId?: string;\n userName: string;\n updatedAt: string;\n}\n\nexport interface FeishuChatMemberApiRecord {\n openId: string;\n userId?: string;\n userName: string;\n}\n\nexport interface FeishuChatMembersClient {\n listChatMembers(payload: { chatId: string; memberIdType: \"open_id\" }): Promise<FeishuChatMemberApiRecord[]>;\n}\n\ninterface FeishuChatMembersSdkPageRecord {\n member_id?: string;\n name?: string;\n user_id?: string;\n}\n\ninterface FeishuChatMembersSdkResponse {\n data?: {\n items?: FeishuChatMembersSdkPageRecord[];\n page_token?: string;\n has_more?: boolean;\n };\n}\n\ninterface FeishuChatMembersSdkClientLike {\n im: {\n v1?: {\n chatMembers?: {\n get(payload: {\n params: { member_id_type: \"open_id\"; page_token?: string };\n path: { chat_id: string };\n }): Promise<FeishuChatMembersSdkResponse>;\n };\n };\n };\n}\n\nexport interface FeishuMemberResolverOptions {\n repository: FeishuMemberRepository;\n client: FeishuChatMembersClient;\n logger?: Pick<Console, \"warn\">;\n now?: () => Date;\n ttlMs?: number;\n}\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000;\n\nexport class FeishuMemberRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n upsert(record: FeishuChatMemberRecord): void {\n this.database\n .prepare(\n `\n INSERT INTO feishu_chat_members (chat_id, open_id, user_id, user_name, updated_at)\n VALUES (@chatId, @openId, @userId, @userName, @updatedAt)\n ON CONFLICT(chat_id, open_id)\n DO UPDATE SET\n user_id = excluded.user_id,\n user_name = excluded.user_name,\n updated_at = excluded.updated_at\n `,\n )\n .run({\n chatId: record.chatId,\n openId: record.openId,\n userId: record.userId ?? null,\n userName: record.userName,\n updatedAt: record.updatedAt,\n });\n }\n\n get(chatId: string, openId: string): FeishuChatMemberRecord | null {\n const row = this.database\n .prepare(\n `\n SELECT\n chat_id AS chatId,\n open_id AS openId,\n user_id AS userId,\n user_name AS userName,\n updated_at AS updatedAt\n FROM feishu_chat_members\n WHERE chat_id = ? AND open_id = ?\n `,\n )\n .get(chatId, openId) as FeishuChatMemberRecord | undefined;\n\n return row ?? null;\n }\n\n listByChat(chatId: string): FeishuChatMemberRecord[] {\n return this.database\n .prepare(\n `\n SELECT\n chat_id AS chatId,\n open_id AS openId,\n user_id AS userId,\n user_name AS userName,\n updated_at AS updatedAt\n FROM feishu_chat_members\n WHERE chat_id = ?\n ORDER BY user_name ASC, open_id ASC\n `,\n )\n .all(chatId) as FeishuChatMemberRecord[];\n }\n\n findUniqueByName(chatId: string, userName: string): FeishuChatMemberRecord | null {\n const rows = this.database\n .prepare(\n `\n SELECT\n chat_id AS chatId,\n open_id AS openId,\n user_id AS userId,\n user_name AS userName,\n updated_at AS updatedAt\n FROM feishu_chat_members\n WHERE chat_id = ? AND user_name = ?\n ORDER BY open_id ASC\n LIMIT 2\n `,\n )\n .all(chatId, userName) as FeishuChatMemberRecord[];\n\n return rows.length === 1 ? rows[0]! : null;\n }\n}\n\nexport class FeishuMemberResolver {\n private readonly now: () => Date;\n private readonly ttlMs: number;\n private readonly logger?: Pick<Console, \"warn\">;\n\n constructor(private readonly options: FeishuMemberResolverOptions) {\n this.now = options.now ?? (() => new Date());\n this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;\n this.logger = options.logger;\n }\n\n async resolveOpenIdName(chatId: string, openId: string): Promise<string> {\n const cached = this.options.repository.get(chatId, openId);\n\n if (!cached || this.isExpired(cached.updatedAt)) {\n try {\n await this.refreshChatMembers(chatId);\n } catch (error) {\n this.logger?.warn(\"Failed to refresh Feishu chat members for open id resolution\", {\n chatId,\n openId,\n error,\n });\n return cached?.userName ?? openId;\n }\n }\n\n return this.options.repository.get(chatId, openId)?.userName ?? openId;\n }\n\n async resolveUniqueName(chatId: string, userName: string): Promise<FeishuChatMemberRecord | null> {\n const cached = this.options.repository.findUniqueByName(chatId, userName);\n if (cached && !this.isExpired(cached.updatedAt)) {\n return cached;\n }\n\n try {\n await this.refreshChatMembers(chatId);\n } catch (error) {\n this.logger?.warn(\"Failed to refresh Feishu chat members for unique name resolution\", {\n chatId,\n userName,\n error,\n });\n return cached ?? null;\n }\n\n return this.options.repository.findUniqueByName(chatId, userName);\n }\n\n private isExpired(updatedAt: string): boolean {\n const updatedAtMs = Date.parse(updatedAt);\n if (Number.isNaN(updatedAtMs)) {\n return true;\n }\n\n return this.now().getTime() - updatedAtMs >= this.ttlMs;\n }\n\n private async refreshChatMembers(chatId: string): Promise<void> {\n const members = await this.options.client.listChatMembers({ chatId, memberIdType: \"open_id\" });\n const updatedAt = this.now().toISOString();\n\n for (const member of members) {\n this.options.repository.upsert({\n chatId,\n openId: member.openId,\n userId: member.userId,\n userName: member.userName,\n updatedAt,\n });\n }\n }\n}\n\nexport function formatFeishuMemberPrompt(members: FeishuChatMemberRecord[], limit = 80): string {\n const lines = members\n .filter((member) => member.userName)\n .slice(0, limit)\n .map((member) => `${member.openId} = ${member.userName}`);\n return lines.length ? `当前群聊成员 ID 与群昵称映射:\\n${lines.join(\"\\n\")}` : \"\";\n}\n\nexport function createFeishuChatMembersClient(client: FeishuChatMembersSdkClientLike): FeishuChatMembersClient {\n return {\n async listChatMembers(payload) {\n const api = client.im.v1?.chatMembers?.get;\n if (!api) {\n throw new Error(\"当前飞书 SDK 不支持 chatMembers.get,无法获取群成员。\");\n }\n\n const members: FeishuChatMemberApiRecord[] = [];\n let pageToken: string | undefined;\n\n do {\n const response = await api({\n path: { chat_id: payload.chatId },\n params: {\n member_id_type: payload.memberIdType,\n ...(pageToken ? { page_token: pageToken } : {}),\n },\n });\n const items = response.data?.items ?? [];\n\n for (const item of items) {\n if (!item.member_id || !item.name) {\n continue;\n }\n\n members.push({\n openId: item.member_id,\n userId: item.user_id,\n userName: item.name,\n });\n }\n\n pageToken = response.data?.has_more ? response.data.page_token : undefined;\n } while (pageToken);\n\n return members;\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\nfunction mapQaLogRow(row: QaLogRow): QaLogRecord {\n return {\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\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(mapQaLogRow);\n }\n\n listRecentByChat(chatId: string, 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 WHERE chat_id = ? AND status = 'answered'\n ORDER BY created_at DESC\n LIMIT ?\n `,\n )\n .all(chatId, clampLimit(limit)) as QaLogRow[];\n\n return rows.map(mapQaLogRow);\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 { FeishuMemberRepository, formatFeishuMemberPrompt } from \"./members.js\";\nimport type { FeishuMemberResolver } from \"./members.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 memberRepository?: FeishuMemberRepository;\n memberResolver?: Pick<FeishuMemberResolver, \"resolveUniqueName\">;\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 containsRawToolCallMarkup(content: string): boolean {\n return /<||DSML||tool_calls>|<||DSML||invoke\\s+name=|<tool_call>|<tool_calls>/i.test(content);\n}\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 memberPrompt?: string;\n conversationContext?: string;\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 systemPromptParts = [FEISHU_TOOL_SYSTEM_PROMPT];\n if (input.memberPrompt) {\n systemPromptParts.push(`${input.memberPrompt}\\n回答中遇到上述 ID 时优先使用对应群昵称;没有映射时保留原 ID,不要编造昵称。`);\n }\n if (input.conversationContext) {\n systemPromptParts.push(`${input.conversationContext}\\n这些是当前群聊里最近几轮你和用户的问答,只作为理解省略指代和连续追问的上下文;如果与检索证据冲突,以检索证据为准。`);\n }\n const systemPrompt = systemPromptParts.join(\"\\n\\n\");\n const messages: ChatMessage[] = [\n { role: \"system\", content: systemPrompt },\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 const hasRawToolCallMarkup = containsRawToolCallMarkup(assistantResult.content);\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 if (hasRawToolCallMarkup) {\n break;\n }\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\nfunction formatConversationContext(records: import(\"../rag/qa-logs.js\").QaLogRecord[]): string {\n const lines = records\n .slice()\n .reverse()\n .map((record, index) => `第 ${index + 1} 轮\\n用户:${record.question}\\n助手:${record.answer}`);\n return lines.length ? `近期对话上下文:\\n${lines.join(\"\\n\\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 private memberResolver?: Pick<FeishuMemberResolver, \"resolveUniqueName\">;\n\n constructor(private readonly options: FeishuQuestionHandlerOptions) {\n this.memberResolver = options.memberResolver;\n }\n\n setMemberResolver(memberResolver: Pick<FeishuMemberResolver, \"resolveUniqueName\">): void {\n this.memberResolver = memberResolver;\n }\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 ?? \"OK\");\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 memberResolver: this.memberResolver,\n });\n const allTools: FeishuExecutableTool[] = [...tools, ...cronTools];\n const memberRepository = this.options.memberRepository ?? new FeishuMemberRepository(this.options.database);\n const memberPrompt = formatFeishuMemberPrompt(memberRepository.listByChat(decision.chatId));\n const conversationContext = formatConversationContext(qaLogs.listRecentByChat(decision.chatId, 6));\n const answer = await runFeishuToolLoop({\n question: decision.question,\n now,\n tools: allTools,\n model: this.options.model,\n memberPrompt,\n conversationContext,\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\";\nimport { buildFeishuPostContent, formatTextWithMentions } from \"./markdown-post.js\";\nimport type { SendTextOptions } from \"./markdown-post.js\";\n\nexport type { FeishuTextMention, SendTextOptions } from \"./markdown-post.js\";\n\nexport interface MessageSender {\n sendTextToChat(chatId: string, text: string, options?: SendTextOptions): 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\nfunction isRichTextCompatibilityError(error: unknown): boolean {\n const value = error && typeof error === \"object\" ? error as Record<string, unknown> : {};\n const code = value.code ?? value.errorCode;\n const message = error instanceof Error ? error.message : String(error);\n\n return code === 230001 || /post|msg_type|content|unsupported|invalid/i.test(message);\n}\n\nasync function sendWithTextFallback(input: {\n sendPost: () => Promise<unknown>;\n sendText: () => Promise<unknown>;\n}): Promise<void> {\n try {\n await input.sendPost();\n } catch (error) {\n if (!isRichTextCompatibilityError(error)) {\n throw error;\n }\n await input.sendText();\n }\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, options?: SendTextOptions): Promise<void> {\n const postPayload = {\n data: {\n receive_id: chatId,\n msg_type: \"post\",\n content: JSON.stringify(buildFeishuPostContent(text, options)),\n },\n params: {\n receive_id_type: \"chat_id\" as const,\n },\n };\n const textPayload = {\n data: {\n receive_id: chatId,\n msg_type: \"text\",\n content: JSON.stringify({ text: formatTextWithMentions(text, options) }),\n },\n params: {\n receive_id_type: \"chat_id\" as const,\n },\n };\n\n if (this.client.im.v1?.message.create) {\n await sendWithTextFallback({\n sendPost: () => this.client.im.v1!.message.create(postPayload),\n sendText: () => this.client.im.v1!.message.create(textPayload),\n });\n return;\n }\n\n if (this.client.im.message?.create) {\n await sendWithTextFallback({\n sendPost: () => this.client.im.message!.create(postPayload),\n sendText: () => this.client.im.message!.create(textPayload),\n });\n return;\n }\n\n throw new Error(\"当前飞书 SDK 不支持消息发送接口。\");\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 postPayload = {\n path: {\n message_id: messageId,\n },\n data: {\n msg_type: \"post\",\n content: JSON.stringify(buildFeishuPostContent(text)),\n },\n };\n const textPayload = {\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 sendWithTextFallback({\n sendPost: () => this.client.im.v1!.message.reply!(postPayload),\n sendText: () => this.client.im.v1!.message.reply!(textPayload),\n });\n return;\n }\n\n if (this.client.im.message?.reply) {\n await sendWithTextFallback({\n sendPost: () => this.client.im.message!.reply!(postPayload),\n sendText: () => this.client.im.message!.reply!(textPayload),\n });\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","export interface FeishuTextMention {\n openId: string;\n name: string;\n}\n\nexport interface SendTextOptions {\n mentions?: FeishuTextMention[];\n}\n\ninterface FeishuPostTextElement {\n tag: \"text\";\n text: string;\n style?: string[];\n}\n\ninterface FeishuPostLinkElement {\n tag: \"a\";\n text: string;\n href: string;\n}\n\ninterface FeishuPostAtElement {\n tag: \"at\";\n user_id: string;\n user_name: string;\n}\n\ntype FeishuPostElement = FeishuPostTextElement | FeishuPostLinkElement | FeishuPostAtElement;\n\nexport interface FeishuPostContent {\n post: {\n zh_cn: {\n title: string;\n content: FeishuPostElement[][];\n };\n };\n}\n\nfunction escapeAtText(value: string): string {\n return value.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\nexport function formatTextWithMentions(text: string, options?: SendTextOptions): string {\n const mentions = options?.mentions ?? [];\n if (mentions.length === 0) return text;\n const prefix = mentions\n .map((mention) => `<at user_id=\"${escapeAtText(mention.openId)}\">${escapeAtText(mention.name)}</at>`)\n .join(\" \");\n return `${prefix} ${text}`.trim();\n}\n\nfunction findMarkdownLinkEnd(text: string, start: number): number {\n let depth = 0;\n for (let index = start; index < text.length; index += 1) {\n const char = text[index];\n if (char === \"(\") {\n depth += 1;\n } else if (char === \")\") {\n if (depth === 0) return index;\n depth -= 1;\n }\n }\n return -1;\n}\n\nfunction parseInline(text: string): FeishuPostElement[] {\n const elements: FeishuPostElement[] = [];\n let index = 0;\n\n while (index < text.length) {\n const linkStart = text.indexOf(\"[\", index);\n const boldStarStart = text.indexOf(\"**\", index);\n const boldUnderscoreStart = text.indexOf(\"__\", index);\n const candidates = [linkStart, boldStarStart, boldUnderscoreStart].filter((value) => value >= 0);\n const next = candidates.length ? Math.min(...candidates) : -1;\n\n if (next < 0) {\n elements.push({ tag: \"text\", text: text.slice(index) });\n break;\n }\n\n if (next > index) {\n elements.push({ tag: \"text\", text: text.slice(index, next) });\n }\n\n if (next === linkStart) {\n const labelEnd = text.indexOf(\"](\", next);\n if (labelEnd > next) {\n const hrefStart = labelEnd + 2;\n const hrefEnd = findMarkdownLinkEnd(text, hrefStart);\n const href = hrefEnd >= 0 ? text.slice(hrefStart, hrefEnd) : \"\";\n if (hrefEnd >= 0 && /^https?:\\/\\/\\S+$/.test(href)) {\n elements.push({ tag: \"a\", text: text.slice(next + 1, labelEnd), href });\n index = hrefEnd + 1;\n continue;\n }\n }\n elements.push({ tag: \"text\", text: text[next] });\n index = next + 1;\n continue;\n }\n\n const marker = next === boldStarStart ? \"**\" : \"__\";\n const close = text.indexOf(marker, next + marker.length);\n if (close > next + marker.length) {\n elements.push({ tag: \"text\", text: text.slice(next + marker.length, close), style: [\"bold\"] });\n index = close + marker.length;\n continue;\n }\n\n elements.push({ tag: \"text\", text: marker });\n index = next + marker.length;\n }\n\n const compacted = elements.filter((element) => element.tag !== \"text\" || element.text.length > 0);\n return compacted.length ? compacted : [{ tag: \"text\", text: \" \" }];\n}\n\nfunction pushParagraph(content: FeishuPostElement[][], lines: string[]): void {\n if (lines.length === 0) return;\n content.push(parseInline(lines.join(\"\\n\")));\n lines.length = 0;\n}\n\nfunction parseMarkdownBlocks(markdown: string): FeishuPostElement[][] {\n if (!markdown.trim()) {\n return [[{ tag: \"text\", text: \" \" }]];\n }\n\n const content: FeishuPostElement[][] = [];\n const paragraph: string[] = [];\n const code: string[] = [];\n let inCodeBlock = false;\n\n for (const rawLine of markdown.replace(/\\r\\n/g, \"\\n\").split(\"\\n\")) {\n const line = rawLine.trimEnd();\n\n if (line.startsWith(\"```\")) {\n if (inCodeBlock) {\n content.push([{ tag: \"text\", text: `\\`\\`\\`\\n${code.join(\"\\n\")}\\n\\`\\`\\`` }]);\n code.length = 0;\n inCodeBlock = false;\n } else {\n pushParagraph(content, paragraph);\n inCodeBlock = true;\n }\n continue;\n }\n\n if (inCodeBlock) {\n code.push(rawLine);\n continue;\n }\n\n if (!line.trim()) {\n pushParagraph(content, paragraph);\n continue;\n }\n\n const heading = line.match(/^#{1,6}\\s+(.+)$/);\n if (heading) {\n pushParagraph(content, paragraph);\n content.push([{ tag: \"text\", text: heading[1], style: [\"bold\"] }]);\n continue;\n }\n\n const unordered = line.match(/^[-*]\\s+(.+)$/);\n if (unordered) {\n pushParagraph(content, paragraph);\n content.push(parseInline(`• ${unordered[1]}`));\n continue;\n }\n\n const ordered = line.match(/^(\\d+)\\.\\s+(.+)$/);\n if (ordered) {\n pushParagraph(content, paragraph);\n content.push(parseInline(`${ordered[1]}. ${ordered[2]}`));\n continue;\n }\n\n paragraph.push(line);\n }\n\n if (inCodeBlock) {\n content.push([{ tag: \"text\", text: `\\`\\`\\`\\n${code.join(\"\\n\")}` }]);\n }\n pushParagraph(content, paragraph);\n\n return content.length ? content : [[{ tag: \"text\", text: markdown }]];\n}\n\nexport function buildFeishuPostContent(markdown: string, options?: SendTextOptions): FeishuPostContent {\n const content = parseMarkdownBlocks(markdown);\n const mentions = options?.mentions ?? [];\n\n if (mentions.length) {\n const mentionElements: FeishuPostElement[] = mentions.map((mention) => ({\n tag: \"at\",\n user_id: mention.openId,\n user_name: mention.name,\n }));\n const firstLine = content[0] ?? [];\n const firstText = firstLine[0];\n if (firstText?.tag === \"text\") {\n content[0] = [...mentionElements, { ...firstText, text: ` ${firstText.text}` }, ...firstLine.slice(1)];\n } else {\n content[0] = [...mentionElements, { tag: \"text\", text: \" \" }, ...firstLine];\n }\n }\n\n return {\n post: {\n zh_cn: {\n title: \"\",\n content,\n },\n },\n };\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 senderOpenId = event.sender?.sender_id?.open_id;\n const senderUserId = event.sender?.sender_id?.user_id;\n const senderId =\n senderOpenId ||\n senderUserId ||\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 sender: {\n ...(senderOpenId ? { openId: senderOpenId } : {}),\n ...(senderUserId ? { userId: senderUserId } : {}),\n },\n attachment: extractFeishuAttachment(messageType, content),\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 { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport type { FeishuMemberResolver } from \"../feishu/members.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 extractFeishuSenderOpenId(message: IngestMessageInput): string | undefined {\n const raw = message.rawPayload;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const sender = (raw as { sender?: unknown }).sender;\n if (!sender || typeof sender !== \"object\" || Array.isArray(sender)) return undefined;\n const openId = (sender as { openId?: unknown }).openId;\n return typeof openId === \"string\" && openId.trim() ? openId.trim() : undefined;\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(public readonly 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 ingestFeishuEventWithMembers(input: {\n payload: FeishuReceiveMessageEvent;\n memberResolver: Pick<FeishuMemberResolver, \"resolveOpenIdName\">;\n }): Promise<GatewayIngestResult> {\n const normalized = normalizeFeishuReceiveMessageEvent(input.payload);\n if (!normalized) {\n return {\n accepted: false,\n reason: \"事件不是可入库的飞书消息。\",\n };\n }\n\n const openId = extractFeishuSenderOpenId(normalized);\n const senderName = openId\n ? await input.memberResolver.resolveOpenIdName(normalized.platformChatId, openId)\n : normalized.senderName;\n const enriched = { ...normalized, senderName };\n const duplicate = this.messages.hasPlatformMessage(enriched.platform, enriched.platformMessageId);\n const messageId = this.messages.ingest(enriched);\n return {\n accepted: true,\n messageId,\n message: enriched,\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 memberResolver?: Pick<FeishuMemberResolver, \"resolveOpenIdName\">;\n }): Promise<GatewayIngestAndDownloadResult> {\n const result = input.memberResolver\n ? await this.ingestFeishuEventWithMembers({ payload: input.payload, memberResolver: input.memberResolver })\n : 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 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 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 { 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 { MessageSearchScope } from \"../messages/types.js\";\nimport { cosineSimilarity } from \"./embedding.js\";\nimport type { EvidenceBlock } from \"./types.js\";\n\nexport interface VectorRecord {\n id: string;\n vector: number[];\n evidence: EvidenceBlock;\n}\n\nexport interface VectorSearchResult extends EvidenceBlock {\n vectorScore: number;\n}\n\nexport interface VectorStore {\n upsert(records: VectorRecord[]): Promise<void>;\n search(vector: number[], limit: number, scope?: MessageSearchScope): Promise<VectorSearchResult[]>;\n}\n\nexport class MemoryVectorStore implements VectorStore {\n private readonly records = new Map<string, VectorRecord>();\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n this.records.set(record.id, record);\n }\n }\n\n async search(vector: number[], limit: number, _scope?: MessageSearchScope): Promise<VectorSearchResult[]> {\n return [...this.records.values()]\n .map((record) => {\n const vectorScore = cosineSimilarity(vector, record.vector);\n return {\n ...record.evidence,\n score: vectorScore,\n vectorScore,\n };\n })\n .sort((left, right) => right.vectorScore - left.vectorScore)\n .slice(0, limit);\n }\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 <path...></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(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\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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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAigBT;AAMA,SAAS,WAAW,OAA2B,UAAkB,KAAqB;AACpF,QA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","names":["path","os","path","path","fs","path","path","fs","deleted","fs","path","path","fs","fs","crypto","path","fs","path","fs","path","path","fs","path","fs","crypto","nowIso","crypto","crypto","nowIso","stableId","crypto","escapeFtsQuery","buildScopeWhere","toEvidenceSource","buildScopeWhere","fs","fs","path","path","fs","fs","path","lark","path","parseCronSchedule","matchesParsedSchedule","parseMinuteField","parseExactOrWildcardField","parseExactNumber","toEvidenceSource","crypto","nowIso","stableId","path","crypto","fs","fs","path","asObject","fileKey","lark","fs","path","path","fs","crypto","fs","path","fs","path","path","crypto","fs","crypto","crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/config/schema.ts","../src/config/store.ts","../src/config/paths.ts","../src/config/update.ts","../src/cron/generator.ts","../src/cron/jobs.ts","../src/cron/schedule.ts","../src/cron/scheduler.ts","../src/cron/tools.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/gateway.ts","../src/gateway/indexing-scheduler.ts","../src/rag/indexer.ts","../src/rag/manual-index.ts","../src/multimodal/tasks.ts","../src/multimodal/worker.ts","../src/feishu/members.ts","../src/rag/qa-logs.ts","../src/feishu/question.ts","../src/feishu/sender.ts","../src/feishu/markdown-post.ts","../src/feishu/normalize.ts","../src/feishu/resource-downloader.ts","../src/files/ingest.ts","../src/files/parser.ts","../src/gateway/ingest.ts","../src/rag/answer.ts","../src/rag/citations.ts","../src/rag/qa-service.ts","../src/rag/vector-store.ts","../src/web/server.ts"],"sourcesContent":["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 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\";\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 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 memberPrompt?: string;\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 systemPrompt = input.memberPrompt\n ? `${SYSTEM_PROMPT}\\n\\n${input.memberPrompt}\\n生成消息时遇到上述 ID 时优先使用对应群昵称;没有映射时保留原 ID,不要编造昵称。`\n : SYSTEM_PROMPT;\n const messages: ChatMessage[] = [\n { role: \"system\", content: systemPrompt },\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: systemPrompt },\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 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 mentionTargetName?: string;\n mentionOpenId?: string;\n mentionUserId?: 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 mentionTargetName: string | null;\n mentionOpenId: string | null;\n mentionUserId: 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 mentionTargetName?: string;\n mentionOpenId?: string;\n mentionUserId?: string;\n }): CronJobRecord {\n const schedule = input.schedule.trim();\n const prompt = input.prompt.trim();\n const imageFileName = input.imageFileName?.trim();\n const mentionTargetName = input.mentionTargetName?.trim();\n const mentionOpenId = input.mentionOpenId?.trim();\n const mentionUserId = input.mentionUserId?.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 ...(mentionTargetName ? { mentionTargetName } : {}),\n ...(mentionOpenId ? { mentionOpenId } : {}),\n ...(mentionUserId ? { mentionUserId } : {}),\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,\n mention_target_name, mention_open_id, mention_user_id, status,\n last_run_at, next_run_at, last_error, created_at, updated_at\n )\n VALUES (\n @id, @chatId, @createdByOpenId, @schedule, @prompt, @imageFileName,\n @mentionTargetName, @mentionOpenId, @mentionUserId, @status,\n NULL, @nextRunAt, NULL, @createdAt, @updatedAt\n )\n `,\n )\n .run({\n ...record,\n imageFileName: record.imageFileName ?? null,\n mentionTargetName: record.mentionTargetName ?? null,\n mentionOpenId: record.mentionOpenId ?? null,\n mentionUserId: record.mentionUserId ?? 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 mention_target_name AS mentionTargetName,\n mention_open_id AS mentionOpenId,\n mention_user_id AS mentionUserId,\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 mentionTargetName: row.mentionTargetName ?? undefined,\n mentionOpenId: row.mentionOpenId ?? undefined,\n mentionUserId: row.mentionUserId ?? 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 mention_target_name AS mentionTargetName,\n mention_open_id AS mentionOpenId,\n mention_user_id AS mentionUserId,\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 mentionTargetName: row.mentionTargetName ?? undefined,\n mentionOpenId: row.mentionOpenId ?? undefined,\n mentionUserId: row.mentionUserId ?? 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 { SendTextOptions } from \"../feishu/sender.js\";\nimport 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, options?: SendTextOptions) => 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 const sendOptions = job.mentionOpenId && job.mentionTargetName\n ? { mentions: [{ openId: job.mentionOpenId, name: job.mentionTargetName }] }\n : undefined;\n if (sendOptions) {\n await options.sendTextToChat(job.chatId, text, sendOptions);\n } else {\n await options.sendTextToChat(job.chatId, text);\n }\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","import type { FeishuMemberResolver } from \"../feishu/members.js\";\nimport 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 memberResolver?: Pick<FeishuMemberResolver, \"resolveUniqueName\">;\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 mentionTargetName: {\n type: \"string\",\n description: \"Optional exact Feishu chat nickname to @ when the scheduled message is sent.\",\n },\n },\n required: [\"schedule\", \"prompt\"],\n additionalProperties: false,\n },\n execute: async (rawInput) => {\n const mentionTargetName = readOptionalString(rawInput, \"mentionTargetName\");\n const mentionTarget = mentionTargetName && input.memberResolver\n ? await input.memberResolver.resolveUniqueName(input.chatId, mentionTargetName)\n : null;\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 mentionTargetName,\n mentionOpenId: mentionTarget?.openId,\n mentionUserId: mentionTarget?.userId,\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 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 feishu_chat_members (\n chat_id TEXT NOT NULL,\n open_id TEXT NOT NULL,\n user_id TEXT,\n user_name TEXT,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (chat_id, open_id)\n );\n\n CREATE INDEX IF NOT EXISTS feishu_chat_members_chat_name_idx\n ON feishu_chat_members(chat_id, user_name);\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 CREATE TABLE IF NOT EXISTS feishu_chat_members (\n chat_id TEXT NOT NULL,\n open_id TEXT NOT NULL,\n user_id TEXT,\n user_name TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (chat_id, open_id)\n );\n\n CREATE INDEX IF NOT EXISTS feishu_chat_members_chat_name_idx\n ON feishu_chat_members(chat_id, user_name);\n `);\n\n const cronJobColumns = database.prepare(\"PRAGMA table_info(cron_jobs)\").all() as Array<{ name: string }>;\n const ensureCronJobColumn = (name: string, definition: string): void => {\n if (!cronJobColumns.some((column) => column.name === name)) {\n database.prepare(`ALTER TABLE cron_jobs ADD COLUMN ${definition}`).run();\n }\n };\n\n ensureCronJobColumn(\"image_file_name\", \"image_file_name TEXT\");\n ensureCronJobColumn(\"mention_target_name\", \"mention_target_name TEXT\");\n ensureCronJobColumn(\"mention_open_id\", \"mention_open_id TEXT\");\n ensureCronJobColumn(\"mention_user_id\", \"mention_user_id TEXT\");\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\nconst OPENAI_EMBEDDING_BATCH_SIZE = 64;\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 parseToolCallArguments(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return {};\n }\n}\n\nfunction decodeDsmlValue(value: string, isString: boolean): unknown {\n const trimmed = value.trim();\n if (isString) {\n return trimmed;\n }\n\n if (trimmed === \"true\") return true;\n if (trimmed === \"false\") return false;\n if (trimmed === \"null\") return null;\n\n const numberValue = Number(trimmed);\n if (trimmed && Number.isFinite(numberValue)) {\n return numberValue;\n }\n\n return trimmed;\n}\n\nfunction parseDsmlToolCalls(content: string | undefined): ToolCall[] {\n if (!content?.includes(\"DSML\")) {\n return [];\n }\n\n const toolCalls: ToolCall[] = [];\n const invokePattern = /<||DSML||invoke\\s+name=\"([^\"]+)\"\\s*>([\\s\\S]*?)<\\/||DSML||invoke>/g;\n const parameterPattern = /<||DSML||parameter\\s+name=\"([^\"]+)\"\\s+string=\"(true|false)\"\\s*>([\\s\\S]*?)<\\/||DSML||parameter>/g;\n\n for (const invoke of content.matchAll(invokePattern)) {\n const name = invoke[1];\n if (!name) {\n continue;\n }\n\n const input: Record<string, unknown> = {};\n const body = invoke[2] ?? \"\";\n for (const parameter of body.matchAll(parameterPattern)) {\n const parameterName = parameter[1];\n if (!parameterName) {\n continue;\n }\n input[parameterName] = decodeDsmlValue(parameter[3] ?? \"\", parameter[2] === \"true\");\n }\n\n toolCalls.push({\n id: `dsml_${toolCalls.length + 1}`,\n name,\n input,\n });\n }\n\n return toolCalls;\n}\n\nfunction parseToolCalls(message?: OpenAICompatibleMessage): ToolCall[] {\n const standardToolCalls =\n message?.tool_calls?.map((toolCall) => ({\n id: toolCall.id,\n name: toolCall.function.name,\n input: parseToolCallArguments(toolCall.function.arguments),\n })) ?? [];\n\n return standardToolCalls.length > 0 ? standardToolCalls : parseDsmlToolCalls(message?.content);\n}\n\nfunction isDsmlToolCallContent(content: string | undefined): boolean {\n return parseDsmlToolCalls(content).length > 0;\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 const toolCalls = parseToolCalls(message);\n\n return {\n content: toolCalls.length > 0 && isDsmlToolCallContent(message?.content) ? \"\" : (message?.content ?? \"\"),\n toolCalls,\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 vectors: number[][] = [];\n for (let index = 0; index < texts.length; index += OPENAI_EMBEDDING_BATCH_SIZE) {\n vectors.push(...(await this.fetchEmbeddingBatch(texts.slice(index, index + OPENAI_EMBEDDING_BATCH_SIZE))));\n }\n return vectors;\n }\n\n private async fetchEmbeddingBatch(texts: string[]): Promise<number[][]> {\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 * 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 { createFeishuChatMembersClient, FeishuMemberRepository, FeishuMemberResolver, formatFeishuMemberPrompt } from \"./members.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 memberResolver?: FeishuMemberResolver;\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 let result: GatewayIngestAndDownloadResult;\n if (options.resourceDownloader) {\n result = await options.ingestor.ingestFeishuEventAndDownloadAttachments({\n payload,\n downloader: options.resourceDownloader,\n config: options.config,\n secrets: options.secrets,\n vectorIndexMessage: options.attachmentVectorIndexer,\n memberResolver: options.memberResolver,\n });\n } else if (options.memberResolver) {\n result = await options.ingestor.ingestFeishuEventWithMembers({\n payload,\n memberResolver: options.memberResolver,\n });\n } else {\n result = options.ingestor.ingestFeishuEvent(payload);\n }\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 memberResolver = new FeishuMemberResolver({\n repository: new FeishuMemberRepository(options.ingestor.database),\n client: createFeishuChatMembersClient(new lark.Client({\n appId: options.config.feishu.appId,\n appSecret: options.secrets.feishu.appSecret,\n domain: mapDomain(options.config.feishu.domain),\n }) as Parameters<typeof createFeishuChatMembersClient>[0]),\n });\n options.questionHandler?.setMemberResolver?.(memberResolver);\n\n const eventDispatcher = createFeishuEventDispatcher({\n config: options.config,\n secrets: options.secrets,\n ingestor: options.ingestor,\n memberResolver,\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, sendOptions) => options.cronJobProcessor!.sender.sendTextToChat(chatId, text, sendOptions),\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 const memberPrompt = formatFeishuMemberPrompt(\n new FeishuMemberRepository(options.cronJobProcessor!.database).listByChat(job.chatId),\n );\n return await generateCronJobMessage({\n prompt: job.prompt,\n model: options.cronJobProcessor!.model,\n tools,\n now,\n memberPrompt,\n });\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","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\nconst EMBEDDING_INDEX_BATCH_SIZE = 64;\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: number[][] = [];\n for (let index = 0; index < chunks.length; index += EMBEDDING_INDEX_BATCH_SIZE) {\n vectors.push(\n ...(await input.embedding.embedBatch(\n chunks.slice(index, index + EMBEDDING_INDEX_BATCH_SIZE).map((chunk) => chunk.text),\n )),\n );\n }\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 { SqliteDatabase } from \"../db/database.js\";\n\nexport interface FeishuChatMemberRecord {\n chatId: string;\n openId: string;\n userId?: string;\n userName: string;\n updatedAt: string;\n}\n\nexport interface FeishuChatMemberApiRecord {\n openId: string;\n userId?: string;\n userName: string;\n}\n\nexport interface FeishuChatMembersClient {\n listChatMembers(payload: { chatId: string; memberIdType: \"open_id\" }): Promise<FeishuChatMemberApiRecord[]>;\n}\n\ninterface FeishuChatMembersSdkPageRecord {\n member_id?: string;\n name?: string;\n user_id?: string;\n}\n\ninterface FeishuChatMembersSdkResponse {\n data?: {\n items?: FeishuChatMembersSdkPageRecord[];\n page_token?: string;\n has_more?: boolean;\n };\n}\n\ninterface FeishuChatMembersSdkClientLike {\n im: {\n v1?: {\n chatMembers?: {\n get(payload: {\n params: { member_id_type: \"open_id\"; page_token?: string };\n path: { chat_id: string };\n }): Promise<FeishuChatMembersSdkResponse>;\n };\n };\n };\n}\n\nexport interface FeishuMemberResolverOptions {\n repository: FeishuMemberRepository;\n client: FeishuChatMembersClient;\n logger?: Pick<Console, \"warn\">;\n now?: () => Date;\n ttlMs?: number;\n}\n\nconst DEFAULT_TTL_MS = 60 * 60 * 1000;\n\nexport class FeishuMemberRepository {\n constructor(private readonly database: SqliteDatabase) {}\n\n upsert(record: FeishuChatMemberRecord): void {\n this.database\n .prepare(\n `\n INSERT INTO feishu_chat_members (chat_id, open_id, user_id, user_name, updated_at)\n VALUES (@chatId, @openId, @userId, @userName, @updatedAt)\n ON CONFLICT(chat_id, open_id)\n DO UPDATE SET\n user_id = excluded.user_id,\n user_name = excluded.user_name,\n updated_at = excluded.updated_at\n `,\n )\n .run({\n chatId: record.chatId,\n openId: record.openId,\n userId: record.userId ?? null,\n userName: record.userName,\n updatedAt: record.updatedAt,\n });\n }\n\n get(chatId: string, openId: string): FeishuChatMemberRecord | null {\n const row = this.database\n .prepare(\n `\n SELECT\n chat_id AS chatId,\n open_id AS openId,\n user_id AS userId,\n user_name AS userName,\n updated_at AS updatedAt\n FROM feishu_chat_members\n WHERE chat_id = ? AND open_id = ?\n `,\n )\n .get(chatId, openId) as FeishuChatMemberRecord | undefined;\n\n return row ?? null;\n }\n\n listByChat(chatId: string): FeishuChatMemberRecord[] {\n return this.database\n .prepare(\n `\n SELECT\n chat_id AS chatId,\n open_id AS openId,\n user_id AS userId,\n user_name AS userName,\n updated_at AS updatedAt\n FROM feishu_chat_members\n WHERE chat_id = ?\n ORDER BY user_name ASC, open_id ASC\n `,\n )\n .all(chatId) as FeishuChatMemberRecord[];\n }\n\n findUniqueByName(chatId: string, userName: string): FeishuChatMemberRecord | null {\n const rows = this.database\n .prepare(\n `\n SELECT\n chat_id AS chatId,\n open_id AS openId,\n user_id AS userId,\n user_name AS userName,\n updated_at AS updatedAt\n FROM feishu_chat_members\n WHERE chat_id = ? AND user_name = ?\n ORDER BY open_id ASC\n LIMIT 2\n `,\n )\n .all(chatId, userName) as FeishuChatMemberRecord[];\n\n return rows.length === 1 ? rows[0]! : null;\n }\n}\n\nexport class FeishuMemberResolver {\n private readonly now: () => Date;\n private readonly ttlMs: number;\n private readonly logger?: Pick<Console, \"warn\">;\n\n constructor(private readonly options: FeishuMemberResolverOptions) {\n this.now = options.now ?? (() => new Date());\n this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;\n this.logger = options.logger;\n }\n\n async resolveOpenIdName(chatId: string, openId: string): Promise<string> {\n const cached = this.options.repository.get(chatId, openId);\n\n if (!cached || this.isExpired(cached.updatedAt)) {\n try {\n await this.refreshChatMembers(chatId);\n } catch (error) {\n this.logger?.warn(\"Failed to refresh Feishu chat members for open id resolution\", {\n chatId,\n openId,\n error,\n });\n return cached?.userName ?? openId;\n }\n }\n\n return this.options.repository.get(chatId, openId)?.userName ?? openId;\n }\n\n async resolveUniqueName(chatId: string, userName: string): Promise<FeishuChatMemberRecord | null> {\n const cached = this.options.repository.findUniqueByName(chatId, userName);\n if (cached && !this.isExpired(cached.updatedAt)) {\n return cached;\n }\n\n try {\n await this.refreshChatMembers(chatId);\n } catch (error) {\n this.logger?.warn(\"Failed to refresh Feishu chat members for unique name resolution\", {\n chatId,\n userName,\n error,\n });\n return cached ?? null;\n }\n\n return this.options.repository.findUniqueByName(chatId, userName);\n }\n\n private isExpired(updatedAt: string): boolean {\n const updatedAtMs = Date.parse(updatedAt);\n if (Number.isNaN(updatedAtMs)) {\n return true;\n }\n\n return this.now().getTime() - updatedAtMs >= this.ttlMs;\n }\n\n private async refreshChatMembers(chatId: string): Promise<void> {\n const members = await this.options.client.listChatMembers({ chatId, memberIdType: \"open_id\" });\n const updatedAt = this.now().toISOString();\n\n for (const member of members) {\n this.options.repository.upsert({\n chatId,\n openId: member.openId,\n userId: member.userId,\n userName: member.userName,\n updatedAt,\n });\n }\n }\n}\n\nexport function formatFeishuMemberPrompt(members: FeishuChatMemberRecord[], limit = 80): string {\n const lines = members\n .filter((member) => member.userName)\n .slice(0, limit)\n .map((member) => `${member.openId} = ${member.userName}`);\n return lines.length ? `当前群聊成员 ID 与群昵称映射:\\n${lines.join(\"\\n\")}` : \"\";\n}\n\nexport function createFeishuChatMembersClient(client: FeishuChatMembersSdkClientLike): FeishuChatMembersClient {\n return {\n async listChatMembers(payload) {\n const api = client.im.v1?.chatMembers?.get;\n if (!api) {\n throw new Error(\"当前飞书 SDK 不支持 chatMembers.get,无法获取群成员。\");\n }\n\n const members: FeishuChatMemberApiRecord[] = [];\n let pageToken: string | undefined;\n\n do {\n const response = await api({\n path: { chat_id: payload.chatId },\n params: {\n member_id_type: payload.memberIdType,\n ...(pageToken ? { page_token: pageToken } : {}),\n },\n });\n const items = response.data?.items ?? [];\n\n for (const item of items) {\n if (!item.member_id || !item.name) {\n continue;\n }\n\n members.push({\n openId: item.member_id,\n userId: item.user_id,\n userName: item.name,\n });\n }\n\n pageToken = response.data?.has_more ? response.data.page_token : undefined;\n } while (pageToken);\n\n return members;\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\nfunction mapQaLogRow(row: QaLogRow): QaLogRecord {\n return {\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\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(mapQaLogRow);\n }\n\n listRecentByChat(chatId: string, 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 WHERE chat_id = ? AND status = 'answered'\n ORDER BY created_at DESC\n LIMIT ?\n `,\n )\n .all(chatId, clampLimit(limit)) as QaLogRow[];\n\n return rows.map(mapQaLogRow);\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 { FeishuMemberRepository, formatFeishuMemberPrompt } from \"./members.js\";\nimport type { FeishuMemberResolver } from \"./members.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 memberRepository?: FeishuMemberRepository;\n memberResolver?: Pick<FeishuMemberResolver, \"resolveUniqueName\">;\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 containsRawToolCallMarkup(content: string): boolean {\n return /<||DSML||tool_calls>|<||DSML||invoke\\s+name=|<tool_call>|<tool_calls>/i.test(content);\n}\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 memberPrompt?: string;\n conversationContext?: string;\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 systemPromptParts = [FEISHU_TOOL_SYSTEM_PROMPT];\n if (input.memberPrompt) {\n systemPromptParts.push(`${input.memberPrompt}\\n回答中遇到上述 ID 时优先使用对应群昵称;没有映射时保留原 ID,不要编造昵称。`);\n }\n if (input.conversationContext) {\n systemPromptParts.push(`${input.conversationContext}\\n这些是当前群聊里最近几轮你和用户的问答,只作为理解省略指代和连续追问的上下文;如果与检索证据冲突,以检索证据为准。`);\n }\n const systemPrompt = systemPromptParts.join(\"\\n\\n\");\n const messages: ChatMessage[] = [\n { role: \"system\", content: systemPrompt },\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 const hasRawToolCallMarkup = containsRawToolCallMarkup(assistantResult.content);\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 if (hasRawToolCallMarkup) {\n break;\n }\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\nfunction formatConversationContext(records: import(\"../rag/qa-logs.js\").QaLogRecord[]): string {\n const lines = records\n .slice()\n .reverse()\n .map((record, index) => `第 ${index + 1} 轮\\n用户:${record.question}\\n助手:${record.answer}`);\n return lines.length ? `近期对话上下文:\\n${lines.join(\"\\n\\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 private memberResolver?: Pick<FeishuMemberResolver, \"resolveUniqueName\">;\n\n constructor(private readonly options: FeishuQuestionHandlerOptions) {\n this.memberResolver = options.memberResolver;\n }\n\n setMemberResolver(memberResolver: Pick<FeishuMemberResolver, \"resolveUniqueName\">): void {\n this.memberResolver = memberResolver;\n }\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 ?? \"OK\");\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 memberResolver: this.memberResolver,\n });\n const allTools: FeishuExecutableTool[] = [...tools, ...cronTools];\n const memberRepository = this.options.memberRepository ?? new FeishuMemberRepository(this.options.database);\n const memberPrompt = formatFeishuMemberPrompt(memberRepository.listByChat(decision.chatId));\n const conversationContext = formatConversationContext(qaLogs.listRecentByChat(decision.chatId, 6));\n const answer = await runFeishuToolLoop({\n question: decision.question,\n now,\n tools: allTools,\n model: this.options.model,\n memberPrompt,\n conversationContext,\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\";\nimport { buildFeishuPostContent, formatTextWithMentions } from \"./markdown-post.js\";\nimport type { SendTextOptions } from \"./markdown-post.js\";\n\nexport type { FeishuTextMention, SendTextOptions } from \"./markdown-post.js\";\n\nexport interface MessageSender {\n sendTextToChat(chatId: string, text: string, options?: SendTextOptions): 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\nfunction collectErrorFields(error: unknown): unknown[] {\n const fields: unknown[] = [error];\n const value = error && typeof error === \"object\" ? error as Record<string, unknown> : {};\n fields.push(value.code, value.errorCode, value.msg, value.message);\n\n const response = value.response && typeof value.response === \"object\" ? value.response as Record<string, unknown> : {};\n const data = response.data && typeof response.data === \"object\" ? response.data as Record<string, unknown> : {};\n fields.push(data.code, data.errorCode, data.msg, data.message);\n\n return fields;\n}\n\nfunction isRichTextCompatibilityError(error: unknown): boolean {\n return collectErrorFields(error).some((field) => {\n if (field === 230001) return true;\n if (typeof field === \"string\") {\n return /post|msg_type|content|unsupported|invalid/i.test(field);\n }\n return false;\n });\n}\n\nasync function sendWithTextFallback(input: {\n sendPost: () => Promise<unknown>;\n sendText: () => Promise<unknown>;\n}): Promise<void> {\n try {\n await input.sendPost();\n } catch (error) {\n if (!isRichTextCompatibilityError(error)) {\n throw error;\n }\n await input.sendText();\n }\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, options?: SendTextOptions): Promise<void> {\n const postPayload = {\n data: {\n receive_id: chatId,\n msg_type: \"post\",\n content: JSON.stringify(buildFeishuPostContent(text, options)),\n },\n params: {\n receive_id_type: \"chat_id\" as const,\n },\n };\n const textPayload = {\n data: {\n receive_id: chatId,\n msg_type: \"text\",\n content: JSON.stringify({ text: formatTextWithMentions(text, options) }),\n },\n params: {\n receive_id_type: \"chat_id\" as const,\n },\n };\n\n if (this.client.im.v1?.message.create) {\n await sendWithTextFallback({\n sendPost: () => this.client.im.v1!.message.create(postPayload),\n sendText: () => this.client.im.v1!.message.create(textPayload),\n });\n return;\n }\n\n if (this.client.im.message?.create) {\n await sendWithTextFallback({\n sendPost: () => this.client.im.message!.create(postPayload),\n sendText: () => this.client.im.message!.create(textPayload),\n });\n return;\n }\n\n throw new Error(\"当前飞书 SDK 不支持消息发送接口。\");\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 postPayload = {\n path: {\n message_id: messageId,\n },\n data: {\n msg_type: \"post\",\n content: JSON.stringify(buildFeishuPostContent(text)),\n },\n };\n const textPayload = {\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 sendWithTextFallback({\n sendPost: () => this.client.im.v1!.message.reply!(postPayload),\n sendText: () => this.client.im.v1!.message.reply!(textPayload),\n });\n return;\n }\n\n if (this.client.im.message?.reply) {\n await sendWithTextFallback({\n sendPost: () => this.client.im.message!.reply!(postPayload),\n sendText: () => this.client.im.message!.reply!(textPayload),\n });\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","export interface FeishuTextMention {\n openId: string;\n name: string;\n}\n\nexport interface SendTextOptions {\n mentions?: FeishuTextMention[];\n}\n\ninterface FeishuPostTextElement {\n tag: \"text\";\n text: string;\n style?: string[];\n}\n\ninterface FeishuPostLinkElement {\n tag: \"a\";\n text: string;\n href: string;\n}\n\ninterface FeishuPostAtElement {\n tag: \"at\";\n user_id: string;\n user_name: string;\n}\n\ntype FeishuPostElement = FeishuPostTextElement | FeishuPostLinkElement | FeishuPostAtElement;\n\nexport interface FeishuPostContent {\n post: {\n zh_cn: {\n title: string;\n content: FeishuPostElement[][];\n };\n };\n}\n\nfunction escapeAtText(value: string): string {\n return value.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\nexport function formatTextWithMentions(text: string, options?: SendTextOptions): string {\n const mentions = options?.mentions ?? [];\n if (mentions.length === 0) return text;\n const prefix = mentions\n .map((mention) => `<at user_id=\"${escapeAtText(mention.openId)}\">${escapeAtText(mention.name)}</at>`)\n .join(\" \");\n return `${prefix} ${text}`.trim();\n}\n\nfunction findMarkdownLinkEnd(text: string, start: number): number {\n let depth = 0;\n for (let index = start; index < text.length; index += 1) {\n const char = text[index];\n if (char === \"(\") {\n depth += 1;\n } else if (char === \")\") {\n if (depth === 0) return index;\n depth -= 1;\n }\n }\n return -1;\n}\n\nfunction parseInline(text: string): FeishuPostElement[] {\n const elements: FeishuPostElement[] = [];\n let index = 0;\n\n while (index < text.length) {\n const linkStart = text.indexOf(\"[\", index);\n const boldStarStart = text.indexOf(\"**\", index);\n const boldUnderscoreStart = text.indexOf(\"__\", index);\n const candidates = [linkStart, boldStarStart, boldUnderscoreStart].filter((value) => value >= 0);\n const next = candidates.length ? Math.min(...candidates) : -1;\n\n if (next < 0) {\n elements.push({ tag: \"text\", text: text.slice(index) });\n break;\n }\n\n if (next > index) {\n elements.push({ tag: \"text\", text: text.slice(index, next) });\n }\n\n if (next === linkStart) {\n const labelEnd = text.indexOf(\"](\", next);\n if (labelEnd > next) {\n const hrefStart = labelEnd + 2;\n const hrefEnd = findMarkdownLinkEnd(text, hrefStart);\n const href = hrefEnd >= 0 ? text.slice(hrefStart, hrefEnd) : \"\";\n if (hrefEnd >= 0 && /^https?:\\/\\/\\S+$/.test(href)) {\n elements.push({ tag: \"a\", text: text.slice(next + 1, labelEnd), href });\n index = hrefEnd + 1;\n continue;\n }\n }\n elements.push({ tag: \"text\", text: text[next] });\n index = next + 1;\n continue;\n }\n\n const marker = next === boldStarStart ? \"**\" : \"__\";\n const close = text.indexOf(marker, next + marker.length);\n if (close > next + marker.length) {\n elements.push({ tag: \"text\", text: text.slice(next + marker.length, close), style: [\"bold\"] });\n index = close + marker.length;\n continue;\n }\n\n elements.push({ tag: \"text\", text: marker });\n index = next + marker.length;\n }\n\n const compacted = elements.filter((element) => element.tag !== \"text\" || element.text.length > 0);\n return compacted.length ? compacted : [{ tag: \"text\", text: \" \" }];\n}\n\nfunction pushParagraph(content: FeishuPostElement[][], lines: string[]): void {\n if (lines.length === 0) return;\n content.push(parseInline(lines.join(\"\\n\")));\n lines.length = 0;\n}\n\nfunction parseMarkdownBlocks(markdown: string): FeishuPostElement[][] {\n if (!markdown.trim()) {\n return [[{ tag: \"text\", text: \" \" }]];\n }\n\n const content: FeishuPostElement[][] = [];\n const paragraph: string[] = [];\n const code: string[] = [];\n let inCodeBlock = false;\n\n for (const rawLine of markdown.replace(/\\r\\n/g, \"\\n\").split(\"\\n\")) {\n const line = rawLine.trimEnd();\n\n if (line.startsWith(\"```\")) {\n if (inCodeBlock) {\n content.push([{ tag: \"text\", text: `\\`\\`\\`\\n${code.join(\"\\n\")}\\n\\`\\`\\`` }]);\n code.length = 0;\n inCodeBlock = false;\n } else {\n pushParagraph(content, paragraph);\n inCodeBlock = true;\n }\n continue;\n }\n\n if (inCodeBlock) {\n code.push(rawLine);\n continue;\n }\n\n if (!line.trim()) {\n pushParagraph(content, paragraph);\n continue;\n }\n\n const heading = line.match(/^#{1,6}\\s+(.+)$/);\n if (heading) {\n pushParagraph(content, paragraph);\n content.push([{ tag: \"text\", text: heading[1], style: [\"bold\"] }]);\n continue;\n }\n\n const unordered = line.match(/^[-*]\\s+(.+)$/);\n if (unordered) {\n pushParagraph(content, paragraph);\n content.push(parseInline(`• ${unordered[1]}`));\n continue;\n }\n\n const ordered = line.match(/^(\\d+)\\.\\s+(.+)$/);\n if (ordered) {\n pushParagraph(content, paragraph);\n content.push(parseInline(`${ordered[1]}. ${ordered[2]}`));\n continue;\n }\n\n paragraph.push(line);\n }\n\n if (inCodeBlock) {\n content.push([{ tag: \"text\", text: `\\`\\`\\`\\n${code.join(\"\\n\")}` }]);\n }\n pushParagraph(content, paragraph);\n\n return content.length ? content : [[{ tag: \"text\", text: markdown }]];\n}\n\nexport function buildFeishuPostContent(markdown: string, options?: SendTextOptions): FeishuPostContent {\n const content = parseMarkdownBlocks(markdown);\n const mentions = options?.mentions ?? [];\n\n if (mentions.length) {\n const mentionElements: FeishuPostElement[] = mentions.map((mention) => ({\n tag: \"at\",\n user_id: mention.openId,\n user_name: mention.name,\n }));\n const firstLine = content[0] ?? [];\n const firstText = firstLine[0];\n if (firstText?.tag === \"text\") {\n content[0] = [...mentionElements, { ...firstText, text: ` ${firstText.text}` }, ...firstLine.slice(1)];\n } else {\n content[0] = [...mentionElements, { tag: \"text\", text: \" \" }, ...firstLine];\n }\n }\n\n return {\n post: {\n zh_cn: {\n title: \"\",\n content,\n },\n },\n };\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 senderOpenId = event.sender?.sender_id?.open_id;\n const senderUserId = event.sender?.sender_id?.user_id;\n const senderId =\n senderOpenId ||\n senderUserId ||\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 sender: {\n ...(senderOpenId ? { openId: senderOpenId } : {}),\n ...(senderUserId ? { userId: senderUserId } : {}),\n },\n attachment: extractFeishuAttachment(messageType, content),\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 { AppConfig, AppSecrets } from \"../config/schema.js\";\nimport type { SqliteDatabase } from \"../db/database.js\";\nimport type { FeishuMemberResolver } from \"../feishu/members.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 extractFeishuSenderOpenId(message: IngestMessageInput): string | undefined {\n const raw = message.rawPayload;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const sender = (raw as { sender?: unknown }).sender;\n if (!sender || typeof sender !== \"object\" || Array.isArray(sender)) return undefined;\n const openId = (sender as { openId?: unknown }).openId;\n return typeof openId === \"string\" && openId.trim() ? openId.trim() : undefined;\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(public readonly 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 ingestFeishuEventWithMembers(input: {\n payload: FeishuReceiveMessageEvent;\n memberResolver: Pick<FeishuMemberResolver, \"resolveOpenIdName\">;\n }): Promise<GatewayIngestResult> {\n const normalized = normalizeFeishuReceiveMessageEvent(input.payload);\n if (!normalized) {\n return {\n accepted: false,\n reason: \"事件不是可入库的飞书消息。\",\n };\n }\n\n const openId = extractFeishuSenderOpenId(normalized);\n const senderName = openId\n ? await input.memberResolver.resolveOpenIdName(normalized.platformChatId, openId)\n : normalized.senderName;\n const enriched = { ...normalized, senderName };\n const duplicate = this.messages.hasPlatformMessage(enriched.platform, enriched.platformMessageId);\n const messageId = this.messages.ingest(enriched);\n return {\n accepted: true,\n messageId,\n message: enriched,\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 memberResolver?: Pick<FeishuMemberResolver, \"resolveOpenIdName\">;\n }): Promise<GatewayIngestAndDownloadResult> {\n const result = input.memberResolver\n ? await this.ingestFeishuEventWithMembers({ payload: input.payload, memberResolver: input.memberResolver })\n : 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 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 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 { 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 { MessageSearchScope } from \"../messages/types.js\";\nimport { cosineSimilarity } from \"./embedding.js\";\nimport type { EvidenceBlock } from \"./types.js\";\n\nexport interface VectorRecord {\n id: string;\n vector: number[];\n evidence: EvidenceBlock;\n}\n\nexport interface VectorSearchResult extends EvidenceBlock {\n vectorScore: number;\n}\n\nexport interface VectorStore {\n upsert(records: VectorRecord[]): Promise<void>;\n search(vector: number[], limit: number, scope?: MessageSearchScope): Promise<VectorSearchResult[]>;\n}\n\nexport class MemoryVectorStore implements VectorStore {\n private readonly records = new Map<string, VectorRecord>();\n\n async upsert(records: VectorRecord[]): Promise<void> {\n for (const record of records) {\n this.records.set(record.id, record);\n }\n }\n\n async search(vector: number[], limit: number, _scope?: MessageSearchScope): Promise<VectorSearchResult[]> {\n return [...this.records.values()]\n .map((record) => {\n const vectorScore = cosineSimilarity(vector, record.vector);\n return {\n ...record.evidence,\n score: vectorScore,\n vectorScore,\n };\n })\n .sort((left, right) => right.vectorScore - left.vectorScore)\n .slice(0, limit);\n }\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 <meta name=\"color-scheme\" content=\"dark\" />\n <title>ChatterCatcher</title>\n <style>\n :root {\n --bg-primary: #0a0a0f;\n --bg-secondary: #12121a;\n --bg-tertiary: #1a1a28;\n --glass-bg: rgba(255,255,255,0.05);\n --glass-border: rgba(255,255,255,0.1);\n --glass-border-hover: rgba(255,255,255,0.2);\n --glass-shadow: 0 8px 32px rgba(0,0,0,0.3);\n --text-primary: #f0f0f5;\n --text-secondary: #a0a0b0;\n --text-muted: #6e6e80;\n --accent: #64d2ff;\n --accent-hover: #7dd8ff;\n --success: #30d158;\n --warning: #ff9f0a;\n --danger: #ff453a;\n --radius-sm: 8px;\n --radius-md: 12px;\n --radius-lg: 16px;\n --radius-xl: 24px;\n --space-xs: 4px;\n --space-sm: 8px;\n --space-md: 16px;\n --space-lg: 24px;\n --space-xl: 32px;\n --space-2xl: 48px;\n --font-sans: -apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",sans-serif;\n --font-mono: \"SF Mono\",\"Menlo\",\"Consolas\",monospace;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: var(--font-sans);\n background: var(--bg-primary);\n color: var(--text-primary);\n line-height: 1.6;\n -webkit-font-smoothing: antialiased;\n overflow-x: hidden;\n min-height: 100vh;\n }\n .glass {\n background: var(--glass-bg);\n backdrop-filter: blur(20px) saturate(180%);\n -webkit-backdrop-filter: blur(20px) saturate(180%);\n border: 1px solid var(--glass-border);\n border-radius: var(--radius-lg);\n box-shadow: var(--glass-shadow);\n transition: all 0.3s ease;\n }\n .glass:hover { border-color: var(--glass-border-hover); box-shadow: 0 12px 40px rgba(0,0,0,0.4); }\n .gradient-bg {\n background: linear-gradient(135deg,#0a0a0f 0%,#12121a 50%,#1a1a28 100%);\n min-height: 100vh;\n }\n .sidebar {\n position: fixed; left: 0; top: 0; width: 260px; height: 100vh;\n padding: var(--space-lg); display: flex; flex-direction: column; gap: var(--space-md); z-index: 100;\n background: linear-gradient(180deg,rgba(255,255,255,0.08) 0%,rgba(255,255,255,0.02) 100%);\n backdrop-filter: blur(40px) saturate(200%);\n -webkit-backdrop-filter: blur(40px) saturate(200%);\n border-right: 1px solid var(--glass-border);\n }\n .sidebar-logo {\n display: flex; align-items: center; gap: var(--space-sm);\n padding: var(--space-md); font-size: 20px; font-weight: 700;\n color: var(--text-primary); margin-bottom: var(--space-md);\n }\n .logo-icon {\n width: 36px; height: 36px;\n background: linear-gradient(135deg,var(--accent),#5e60ce);\n border-radius: var(--radius-md);\n display: flex; align-items: center; justify-content: center;\n box-shadow: 0 4px 16px rgba(100,210,255,0.3);\n }\n .sidebar-nav { display: flex; flex-direction: column; gap: var(--space-xs); }\n .nav-item {\n display: flex; align-items: center; gap: var(--space-sm);\n padding: var(--space-sm) var(--space-md); border-radius: var(--radius-md);\n color: var(--text-secondary); text-decoration: none; cursor: pointer;\n transition: all 0.2s ease; border: none; background: none;\n font-size: 14px; font-family: inherit; width: 100%; text-align: left;\n }\n .nav-item:hover { background: rgba(255,255,255,0.06); color: var(--text-primary); }\n .nav-item.active {\n background: rgba(100,210,255,0.15); color: var(--accent);\n box-shadow: 0 0 20px rgba(100,210,255,0.1);\n }\n .nav-icon { width: 20px; height: 20px; flex-shrink: 0; }\n .main-content { margin-left: 260px; min-height: 100vh; padding: var(--space-xl); }\n .page-header { margin-bottom: var(--space-xl); }\n .page-title {\n font-size: 36px; font-weight: 700; letter-spacing: -0.03em;\n margin-bottom: var(--space-sm);\n background: linear-gradient(135deg,var(--text-primary),var(--accent));\n -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;\n }\n .page-subtitle { color: var(--text-secondary); font-size: 15px; }\n .metrics-grid {\n display: grid; grid-template-columns: repeat(auto-fit,minmax(200px,1fr));\n gap: var(--space-md); margin-bottom: var(--space-xl);\n }\n .metric-card {\n padding: var(--space-lg); display: flex; flex-direction: column; gap: var(--space-sm);\n position: relative; overflow: hidden;\n }\n .metric-card::before {\n content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;\n background: linear-gradient(90deg,var(--accent),transparent); opacity: 0.5;\n }\n .metric-value { font-size: 40px; font-weight: 700; color: var(--text-primary); line-height: 1; font-variant-numeric: tabular-nums; }\n .metric-label { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; }\n .metric-note { font-size: 13px; color: var(--text-secondary); margin-top: var(--space-xs); }\n .content-grid { display: grid; grid-template-columns: 2fr 1fr; gap: var(--space-lg); }\n .content-panel { padding: var(--space-lg); }\n .panel-header {\n display: flex; justify-content: space-between; align-items: center;\n margin-bottom: var(--space-lg); padding-bottom: var(--space-md);\n border-bottom: 1px solid var(--glass-border);\n }\n .panel-title { font-size: 18px; font-weight: 600; }\n .message-list { display: flex; flex-direction: column; gap: var(--space-sm); }\n .message-card {\n padding: var(--space-md); border-radius: var(--radius-md);\n background: rgba(255,255,255,0.03); border: 1px solid transparent;\n transition: all 0.25s ease; cursor: pointer;\n }\n .message-card:hover { background: rgba(255,255,255,0.06); border-color: var(--glass-border); transform: translateX(4px); }\n .message-meta {\n display: flex; align-items: center; gap: var(--space-md);\n color: var(--text-muted); font-size: 12px; margin-bottom: var(--space-xs); flex-wrap: wrap;\n }\n .message-text { color: var(--text-secondary); font-size: 14px; line-height: 1.6; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }\n .status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }\n .status-dot.online { background: var(--success); box-shadow: 0 0 8px var(--success); }\n .status-dot.offline { background: var(--danger); }\n .status-dot.warning { background: var(--warning); box-shadow: 0 0 8px var(--warning); }\n .status-dot.pending { background: var(--text-muted); }\n .btn {\n display: inline-flex; align-items: center; justify-content: center; gap: var(--space-sm);\n padding: 10px var(--space-md); border-radius: var(--radius-md);\n border: 1px solid var(--glass-border); background: var(--glass-bg);\n color: var(--text-primary); font-family: inherit; font-size: 14px;\n cursor: pointer; transition: all 0.2s ease; text-decoration: none;\n }\n .btn:hover { background: rgba(255,255,255,0.1); border-color: var(--glass-border-hover); transform: translateY(-1px); }\n .btn-primary {\n background: linear-gradient(135deg,var(--accent),#5e60ce); color: white; border: none;\n font-weight: 600; box-shadow: 0 4px 16px rgba(100,210,255,0.3);\n }\n .btn-primary:hover {\n background: linear-gradient(135deg,var(--accent-hover),#6b6dd8);\n box-shadow: 0 6px 20px rgba(100,210,255,0.4); transform: translateY(-1px);\n }\n .btn-danger { background: rgba(255,69,58,0.15); color: var(--danger); border-color: rgba(255,69,58,0.3); }\n .btn-danger:hover { background: rgba(255,69,58,0.25); }\n .btn-sm { padding: 6px var(--space-sm); font-size: 13px; }\n .btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }\n .tag {\n display: inline-flex; align-items: center; padding: 2px 10px;\n border-radius: 20px; font-size: 12px; font-weight: 500;\n background: rgba(255,255,255,0.06); color: var(--text-secondary);\n }\n .tag-success { background: rgba(48,209,88,0.15); color: var(--success); }\n .tag-warning { background: rgba(255,159,10,0.15); color: var(--warning); }\n .tag-error { background: rgba(255,69,58,0.15); color: var(--danger); }\n .tag-info { background: rgba(100,210,255,0.15); color: var(--accent); }\n .empty-state { text-align: center; padding: var(--space-2xl); color: var(--text-muted); }\n .empty-state svg { width: 48px; height: 48px; margin: 0 auto var(--space-md); opacity: 0.3; }\n .skeleton {\n background: linear-gradient(90deg,rgba(255,255,255,0.03) 25%,rgba(255,255,255,0.08) 50%,rgba(255,255,255,0.03) 75%);\n background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: var(--radius-sm);\n }\n @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }\n @keyframes fadeIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }\n @keyframes slideIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } }\n @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }\n .view { display: none; animation: fadeIn 0.35s ease; }\n .view.active { display: block; }\n .search-box { position: relative; width: 100%; max-width: 400px; }\n .search-box input {\n width: 100%; padding: var(--space-sm) var(--space-md) var(--space-sm) 40px;\n border-radius: var(--radius-md); border: 1px solid var(--glass-border);\n background: var(--glass-bg); color: var(--text-primary); font-family: inherit;\n font-size: 14px; outline: none; transition: all 0.2s ease;\n }\n .search-box input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(100,210,255,0.1); }\n .search-box .search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-muted); }\n .data-table { width: 100%; border-collapse: collapse; }\n .data-table th {\n text-align: left; padding: var(--space-sm) var(--space-md); color: var(--text-muted);\n font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;\n border-bottom: 1px solid var(--glass-border);\n }\n .data-table td { padding: var(--space-sm) var(--space-md); color: var(--text-secondary); font-size: 14px; border-bottom: 1px solid rgba(255,255,255,0.03); vertical-align: top; }\n .data-table tr:hover td { background: rgba(255,255,255,0.02); }\n .data-table tr:last-child td { border-bottom: none; }\n .truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }\n .truncate-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }\n .truncate-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }\n .flex { display: flex; } .flex-col { flex-direction: column; }\n .items-center { align-items: center; } .justify-between { justify-content: space-between; }\n .gap-sm { gap: var(--space-sm); } .gap-md { gap: var(--space-md); }\n .mt-md { margin-top: var(--space-md); } .mt-lg { margin-top: var(--space-lg); }\n .mb-md { margin-bottom: var(--space-md); }\n .toast {\n padding: var(--space-md) var(--space-lg); border-radius: var(--radius-md);\n background: var(--glass-bg); backdrop-filter: blur(20px); border: 1px solid var(--glass-border);\n box-shadow: 0 8px 32px rgba(0,0,0,0.4); color: var(--text-primary); font-size: 14px;\n max-width: 400px; animation: slideIn 0.3s ease;\n display: flex; align-items: center; gap: var(--space-sm);\n }\n .toast-success { border-color: rgba(48,209,88,0.3); background: rgba(48,209,88,0.1); }\n .toast-error { border-color: rgba(255,69,58,0.3); background: rgba(255,69,58,0.1); }\n .toast-warning { border-color: rgba(255,159,10,0.3); background: rgba(255,159,10,0.1); }\n .episode-card {\n padding: var(--space-md); border-radius: var(--radius-md);\n background: rgba(255,255,255,0.03); border: 1px solid transparent; transition: all 0.25s ease;\n }\n .episode-card:hover { background: rgba(255,255,255,0.06); border-color: var(--glass-border); }\n .qa-card {\n padding: var(--space-md); border-radius: var(--radius-md);\n background: rgba(255,255,255,0.03); border-left: 3px solid var(--accent); margin-bottom: var(--space-sm);\n }\n .qa-question { font-weight: 600; color: var(--text-primary); margin-bottom: var(--space-xs); font-size: 14px; }\n .qa-answer { color: var(--text-secondary); font-size: 14px; line-height: 1.6; }\n .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-lg); }\n .section-title { font-size: 24px; font-weight: 700; }\n .tabs {\n display: flex; gap: var(--space-xs); padding: 4px;\n background: rgba(255,255,255,0.03); border-radius: var(--radius-md); border: 1px solid var(--glass-border);\n }\n .tab { padding: 8px 16px; border-radius: var(--radius-sm); border: none; background: none; color: var(--text-secondary); font-family: inherit; font-size: 14px; cursor: pointer; transition: all 0.2s ease; }\n .tab:hover { color: var(--text-primary); }\n .tab.active { background: rgba(255,255,255,0.08); color: var(--text-primary); font-weight: 500; }\n .file-card {\n padding: var(--space-md); border-radius: var(--radius-md);\n background: rgba(255,255,255,0.03); border: 1px solid transparent; transition: all 0.25s ease; cursor: pointer;\n }\n .file-card:hover { background: rgba(255,255,255,0.06); border-color: var(--glass-border); }\n .file-icon {\n width: 40px; height: 40px; border-radius: var(--radius-sm);\n background: linear-gradient(135deg,var(--accent),#5e60ce);\n display: flex; align-items: center; justify-content: center; margin-bottom: var(--space-sm);\n }\n .timeline { position: relative; padding-left: 28px; }\n .timeline::before { content: ''; position: absolute; left: 8px; top: 0; bottom: 0; width: 2px; background: linear-gradient(180deg,var(--accent),transparent); opacity: 0.3; }\n .timeline-item { position: relative; padding-bottom: var(--space-lg); }\n .timeline-item::before { content: ''; position: absolute; left: -24px; top: 4px; width: 10px; height: 10px; border-radius: 50%; background: var(--accent); border: 2px solid var(--bg-primary); box-shadow: 0 0 0 2px var(--accent); }\n .timeline-date { font-size: 12px; color: var(--text-muted); margin-bottom: var(--space-xs); }\n .timeline-content { color: var(--text-secondary); font-size: 14px; }\n .status-bar { display: flex; align-items: center; gap: var(--space-md); padding: var(--space-md); margin-bottom: var(--space-lg); }\n .status-item { display: flex; align-items: center; gap: var(--space-sm); }\n .status-label { font-size: 13px; color: var(--text-muted); }\n .status-value { font-size: 14px; font-weight: 600; color: var(--text-primary); }\n .grid-2 { display: grid; grid-template-columns: repeat(2,1fr); gap: var(--space-md); }\n .grid-3 { display: grid; grid-template-columns: repeat(3,1fr); gap: var(--space-md); }\n .settings-group { padding: var(--space-lg); margin-bottom: var(--space-lg); }\n .settings-item { display: flex; justify-content: space-between; align-items: center; padding: var(--space-md) 0; border-bottom: 1px solid var(--glass-border); }\n .settings-item:last-child { border-bottom: none; }\n .settings-label { font-size: 14px; font-weight: 500; color: var(--text-primary); }\n .settings-value { font-size: 14px; color: var(--text-secondary); font-family: var(--font-mono); }\n .settings-desc { font-size: 12px; color: var(--text-muted); margin-top: 2px; }\n .mobile-nav {\n display: none; position: fixed; bottom: 0; left: 0; right: 0;\n padding: var(--space-sm); z-index: 100; flex-direction: row; justify-content: space-around;\n border-top: 1px solid var(--glass-border);\n background: linear-gradient(180deg,rgba(255,255,255,0.08) 0%,rgba(255,255,255,0.02) 100%);\n backdrop-filter: blur(40px) saturate(200%);\n }\n .mobile-nav-item { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: var(--space-xs); color: var(--text-secondary); text-decoration: none; cursor: pointer; border: none; background: none; font-size: 10px; font-family: inherit; }\n .mobile-nav-item.active { color: var(--accent); }\n .pulse { animation: pulse 2s cubic-bezier(0.4,0,0.6,1) infinite; }\n @media (max-width: 1024px) {\n .sidebar { width: 72px; padding: var(--space-sm); }\n .sidebar-logo span, .nav-item span { display: none; }\n .nav-item { justify-content: center; padding: var(--space-sm); }\n .main-content { margin-left: 72px; padding: var(--space-lg); }\n .content-grid { grid-template-columns: 1fr; }\n .metrics-grid { grid-template-columns: repeat(2,1fr); }\n }\n @media (max-width: 768px) {\n .sidebar { display: none; }\n .mobile-nav { display: flex; }\n .main-content { margin-left: 0; margin-bottom: 80px; padding: var(--space-md); }\n .page-title { font-size: 28px; }\n .metrics-grid { grid-template-columns: repeat(2,1fr); }\n .grid-2, .grid-3 { grid-template-columns: 1fr; }\n .section-header { flex-direction: column; align-items: flex-start; gap: var(--space-sm); }\n }\n @media (prefers-reduced-motion: reduce) {\n *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }\n }\n ::-webkit-scrollbar { width: 8px; height: 8px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 4px; }\n ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); }\n .highlight-text { background: rgba(100,210,255,0.15); padding: 0 4px; border-radius: 3px; color: var(--accent); }\n </style>\n</head>\n<body class=\"gradient-bg\">\n <aside class=\"sidebar\">\n <div class=\"sidebar-logo\">\n <div class=\"logo-icon\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2L2 7l10 5 10-5-10-5z\"/><path d=\"M2 17l10 5 10-5\"/><path d=\"M2 12l10 5 10-5\"/></svg>\n </div>\n <span>ChatterCatcher</span>\n </div>\n <nav class=\"sidebar-nav\">\n <button class=\"nav-item active\" data-view=\"overview\">\n <svg class=\"nav-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/></svg>\n <span>概览</span>\n </button>\n <button class=\"nav-item\" data-view=\"messages\">\n <svg class=\"nav-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>\n <span>消息</span>\n </button>\n <button class=\"nav-item\" data-view=\"episodes\">\n <svg class=\"nav-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z\"/><path d=\"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z\"/></svg>\n <span>会话记忆</span>\n </button>\n <button class=\"nav-item\" data-view=\"files\">\n <svg class=\"nav-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>\n <span>文件库</span>\n </button>\n <button class=\"nav-item\" data-view=\"tasks\">\n <svg class=\"nav-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\"/></svg>\n <span>任务</span>\n </button>\n <button class=\"nav-item\" data-view=\"qa-logs\">\n <svg class=\"nav-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/></svg>\n <span>问答日志</span>\n </button>\n <button class=\"nav-item\" data-view=\"settings\">\n <svg class=\"nav-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>\n <span>设置</span>\n </button>\n </nav>\n <div style=\"margin-top: auto; padding: var(--space-md);\">\n <div style=\"display: flex; align-items: center; gap: var(--space-sm); font-size: 12px; color: var(--text-muted);\">\n <span class=\"status-dot online\" id=\"gateway-indicator\"></span>\n <span id=\"gateway-status-text\">Gateway 运行中</span>\n </div>\n <div style=\"font-size: 11px; color: var(--text-muted); margin-top: var(--space-xs); opacity: 0.7;\" id=\"version-text\">v0.0.0</div>\n </div>\n </aside>\n\n <nav class=\"mobile-nav glass\">\n <button class=\"mobile-nav-item active\" data-view=\"overview\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\"/></svg>\n <span>概览</span>\n </button>\n <button class=\"mobile-nav-item\" data-view=\"messages\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>\n <span>消息</span>\n </button>\n <button class=\"mobile-nav-item\" data-view=\"files\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>\n <span>文件</span>\n </button>\n <button class=\"mobile-nav-item\" data-view=\"tasks\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\"/></svg>\n <span>任务</span>\n </button>\n <button class=\"mobile-nav-item\" data-view=\"settings\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>\n <span>设置</span>\n </button>\n </nav>\n\n <main class=\"main-content\">\n <div class=\"view active\" id=\"view-overview\">\n <div class=\"page-header\">\n <h1 class=\"page-title\">Dashboard</h1>\n <p class=\"page-subtitle\">本地优先的家庭群知识库 · 问答必须先检索 RAG 证据,不堆叠全量上下文</p>\n </div>\n <div class=\"metrics-grid\" id=\"metrics\"></div>\n <div class=\"content-grid\">\n <div>\n <div class=\"content-panel glass\">\n <div class=\"panel-header\">\n <h2 class=\"panel-title\">最近消息</h2>\n <button class=\"btn btn-sm\" onclick=\"navigateTo('messages')\">查看全部</button>\n </div>\n <div id=\"recent-messages\"></div>\n </div>\n <div class=\"content-panel glass mt-lg\">\n <div class=\"panel-header\">\n <h2 class=\"panel-title\">会话记忆</h2>\n <button class=\"btn btn-sm\" onclick=\"navigateTo('episodes')\">查看全部</button>\n </div>\n <div id=\"recent-episodes\"></div>\n </div>\n </div>\n <div>\n <div class=\"content-panel glass\">\n <div class=\"panel-header\"><h2 class=\"panel-title\">系统状态</h2></div>\n <div id=\"system-status\"></div>\n </div>\n <div class=\"content-panel glass mt-lg\">\n <div class=\"panel-header\"><h2 class=\"panel-title\">快捷操作</h2></div>\n <div style=\"display: flex; flex-direction: column; gap: var(--space-sm);\">\n <button class=\"btn btn-primary\" id=\"btn-process-messages\" onclick=\"processNow()\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"5 3 19 12 5 21 5 3\"/></svg>\n 立即处理消息\n </button>\n <button class=\"btn\" onclick=\"navigateTo('settings')\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>\n 系统设置\n </button>\n </div>\n </div>\n <div class=\"content-panel glass mt-lg\">\n <div class=\"panel-header\"><h2 class=\"panel-title\">RAG 检索</h2></div>\n <div style=\"font-size: 13px; color: var(--text-secondary); line-height: 1.8;\">\n <div style=\"display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-sm);\"><span class=\"tag tag-success\">FTS5</span><span>关键词检索</span></div>\n <div style=\"display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-sm);\"><span class=\"tag tag-info\">向量</span><span>语义检索</span></div>\n <div style=\"display: flex; align-items: center; gap: var(--space-sm);\"><span class=\"tag tag-success\">混合</span><span>Hybrid RAG</span></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"view\" id=\"view-messages\">\n <div class=\"section-header\">\n <div><h1 class=\"section-title\">消息</h1><p class=\"page-subtitle\">群聊消息历史</p></div>\n <div class=\"search-box\">\n <svg class=\"search-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/></svg>\n <input type=\"text\" id=\"message-search\" placeholder=\"搜索消息...\" oninput=\"filterMessages()\" />\n </div>\n </div>\n <div class=\"content-panel glass\"><div id=\"messages-list\"></div></div>\n </div>\n\n <div class=\"view\" id=\"view-episodes\">\n <div class=\"section-header\"><div><h1 class=\"section-title\">会话记忆</h1><p class=\"page-subtitle\">自动聚合的聊天片段</p></div></div>\n <div class=\"content-panel glass\"><div id=\"episodes-list\"></div></div>\n </div>\n\n <div class=\"view\" id=\"view-files\">\n <div class=\"section-header\"><div><h1 class=\"section-title\">文件库</h1><p class=\"page-subtitle\">已导入的文件知识源</p></div></div>\n <div id=\"files-list\"></div>\n </div>\n\n <div class=\"view\" id=\"view-tasks\">\n <div class=\"section-header\"><div><h1 class=\"section-title\">任务</h1><p class=\"page-subtitle\">文件解析与定时任务</p></div></div>\n <div class=\"tabs\" style=\"margin-bottom: var(--space-lg);\">\n <button class=\"tab active\" data-tab=\"file-jobs\" onclick=\"switchTab('file-jobs')\">文件解析</button>\n <button class=\"tab\" data-tab=\"cron-jobs\" onclick=\"switchTab('cron-jobs')\">定时任务</button>\n </div>\n <div class=\"content-panel glass\" id=\"tab-file-jobs\"><div id=\"file-jobs-list\"></div></div>\n <div class=\"content-panel glass\" id=\"tab-cron-jobs\" style=\"display: none;\"><div id=\"cron-jobs-list\"></div></div>\n </div>\n\n <div class=\"view\" id=\"view-qa-logs\">\n <div class=\"section-header\"><div><h1 class=\"section-title\">问答日志</h1><p class=\"page-subtitle\">问答历史记录</p></div></div>\n <div class=\"content-panel glass\"><div id=\"qa-logs-list\"></div></div>\n </div>\n\n <div class=\"view\" id=\"view-settings\">\n <div class=\"section-header\"><div><h1 class=\"section-title\">设置</h1><p class=\"page-subtitle\">系统配置与操作</p></div></div>\n <div class=\"settings-group glass\" id=\"settings-config\"></div>\n <div class=\"settings-group glass\">\n <h3 style=\"font-size: 16px; font-weight: 600; margin-bottom: var(--space-md);\">操作</h3>\n <div style=\"display: flex; flex-direction: column; gap: var(--space-sm);\">\n <button class=\"btn btn-primary\" onclick=\"processNow()\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"5 3 19 12 5 21 5 3\"/></svg>\n 立即处理消息索引\n </button>\n <div style=\"font-size: 12px; color: var(--text-muted); padding: var(--space-sm); background: rgba(255,255,255,0.03); border-radius: var(--radius-sm);\">\n 运行 CLI 命令进行更多操作:\n <div style=\"font-family: var(--font-mono); margin-top: var(--space-xs); line-height: 1.8;\">\n chattercatcher settings<br/>\n chattercatcher doctor<br/>\n chattercatcher index rebuild<br/>\n chattercatcher files add <path...><br/>\n chattercatcher export\n </div>\n </div>\n </div>\n </div>\n </div>\n </main>\n\n <div id=\"toast-container\" style=\"position: fixed; top: 24px; right: 24px; z-index: 1001; display: flex; flex-direction: column; gap: 12px;\"></div>\n\n <script>\n let currentView = \"overview\";\n let allMessages = [];\n let allEpisodes = [];\n let allFiles = [];\n let allFileJobs = [];\n let allCronJobs = [];\n let allQaLogs = [];\n let statusData = null;\n\n function fmt(value) { return value == null || value === \"\" ? \"-\" : String(value); }\n function escapeHtml(value) {\n return fmt(value).replaceAll(\"&\", \"&\").replaceAll(\"<\", \"<\").replaceAll(\">\", \">\").replaceAll('\"', \""\");\n }\n function isOpaqueId(value) { return /^(ou|oc|om|cli|on|un|uid)_?[a-z0-9]+/i.test(fmt(value)); }\n function formatDateTime(value) {\n var date = new Date(value);\n if (Number.isNaN(date.getTime())) return fmt(value);\n var pad = function(n) { return String(n).padStart(2, \"0\"); };\n return date.getFullYear() + \"/\" + pad(date.getMonth()+1) + \"/\" + pad(date.getDate()) + \" \" + pad(date.getHours()) + \":\" + pad(date.getMinutes());\n }\n function displaySender(value) { return isOpaqueId(value) ? \"群成员\" : fmt(value); }\n function displayChatName(value, platform) { return !isOpaqueId(value) ? fmt(value) : (platform === \"feishu\" ? \"飞书群聊\" : \"群聊\"); }\n\n function showToast(message, type) {\n type = type || \"info\";\n var container = document.getElementById(\"toast-container\");\n var toast = document.createElement(\"div\");\n toast.className = \"toast toast-\" + type;\n toast.textContent = message;\n container.appendChild(toast);\n setTimeout(function() {\n toast.style.opacity = \"0\"; toast.style.transform = \"translateX(10px)\";\n setTimeout(function() { toast.remove(); }, 300);\n }, 3000);\n }\n\n function navigateTo(view) {\n document.querySelectorAll(\".view\").forEach(function(el) { el.classList.remove(\"active\"); });\n document.querySelectorAll(\".nav-item, .mobile-nav-item\").forEach(function(el) { el.classList.remove(\"active\"); });\n document.getElementById(\"view-\" + view).classList.add(\"active\");\n document.querySelectorAll('[data-view=\"' + view + '\"]').forEach(function(el) { el.classList.add(\"active\"); });\n currentView = view;\n window.scrollTo(0, 0);\n if (view === \"messages\") renderMessagesView();\n if (view === \"episodes\") renderEpisodesView();\n if (view === \"files\") renderFilesView();\n if (view === \"tasks\") renderTasksView();\n if (view === \"qa-logs\") renderQaLogsView();\n }\n\n document.querySelectorAll(\".nav-item, .mobile-nav-item\").forEach(function(el) {\n el.addEventListener(\"click\", function() { navigateTo(el.dataset.view); });\n });\n\n function switchTab(tab) {\n document.querySelectorAll(\".tab\").forEach(function(el) { el.classList.remove(\"active\"); });\n document.querySelector('[data-tab=\"' + tab + '\"]').classList.add(\"active\");\n document.getElementById(\"tab-file-jobs\").style.display = tab === \"file-jobs\" ? \"block\" : \"none\";\n document.getElementById(\"tab-cron-jobs\").style.display = tab === \"cron-jobs\" ? \"block\" : \"none\";\n if (tab === \"file-jobs\") renderFileJobs();\n if (tab === \"cron-jobs\") renderCronJobs();\n }\n\n async function fetchJson(path) {\n var response = await fetch(path);\n if (!response.ok) {\n var body = await response.text();\n throw new Error(path + \" \" + response.status + \" \" + body);\n }\n return response.json();\n }\n\n async function postJson(path, options) {\n var response = await fetch(path, Object.assign({ method: \"POST\" }, options || {}));\n var result = await response.json();\n if (!response.ok) {\n throw new Error(result.message || result.reason || \"请求失败\");\n }\n return result;\n }\n\n async function deleteJson(path) {\n var response = await fetch(path, { method: \"DELETE\" });\n var result = await response.json();\n if (!response.ok) {\n throw new Error(result.message || result.reason || \"请求失败\");\n }\n return result;\n }\n\n function renderMetrics(status) {\n var gatewayClass = status.gateway.configured ? \"status-dot online\" : \"status-dot offline\";\n var gatewayText = status.gateway.connection === \"running\" ? \"运行中\" : (!status.gateway.configured ? \"未配置\" : \"待启动\");\n var metricsHtml = [\n [\"Gateway\", gatewayText, \"飞书长连接\", gatewayClass],\n [\"版本\", status.version || \"unknown\", \"当前运行版本\", \"\"],\n [\"群聊\", status.data.chats, \"本地群聊数\", \"\"],\n [\"消息\", status.data.messages, \"已入库消息\", \"\"],\n [\"会话记忆\", status.data.episodes, \"已生成摘要\", \"\"],\n [\"文件\", status.data.files, \"文件知识源\", \"\"],\n [\"问答\", status.data.qaLogs, \"问答记录\", \"\"],\n [\"任务\", status.data.cronJobs, \"定时任务\", \"\"]\n ].map(function(item) {\n var label = item[0], value = item[1], note = item[2], dotClass = item[3];\n return '<div class=\"metric-card glass\"><div class=\"metric-label\">' + escapeHtml(label) + '</div>' +\n '<div class=\"metric-value\">' + (dotClass ? '<span class=\"' + dotClass + '\" style=\"margin-right:8px;\"></span>' : '') + escapeHtml(value) + '</div>' +\n '<div class=\"metric-note\">' + escapeHtml(note) + '</div></div>';\n }).join(\"\");\n document.getElementById(\"metrics\").innerHTML = metricsHtml;\n document.getElementById(\"gateway-indicator\").className = gatewayClass;\n document.getElementById(\"gateway-status-text\").textContent = \"Gateway \" + gatewayText;\n document.getElementById(\"version-text\").textContent = \"v\" + (status.version || \"unknown\");\n }\n\n function renderSystemStatus(status) {\n var gateway = status.gateway;\n var html = '<div style=\"display:flex;flex-direction:column;gap:var(--space-md);\">';\n html += '<div class=\"settings-item\"><div><div class=\"settings-label\">Gateway</div></div><div class=\"settings-value\">' + (gateway.connection === \"running\" ? '<span class=\"tag tag-success\">运行中</span>' : '<span class=\"tag tag-warning\">未运行</span>') + '</div></div>';\n html += '<div class=\"settings-item\"><div><div class=\"settings-label\">Web UI</div></div><div class=\"settings-value\">' + escapeHtml((status.web && status.web.host ? status.web.host : \"127.0.0.1\") + \":\" + (status.web && status.web.port ? status.web.port : \"3878\")) + '</div></div>';\n html += '<div class=\"settings-item\"><div><div class=\"settings-label\">RAG 模式</div></div><div class=\"settings-value\"><span class=\"tag tag-success\">强制检索</span></div></div>';\n html += '<div class=\"settings-item\"><div><div class=\"settings-label\">关键词检索</div></div><div class=\"settings-value\">SQLite FTS5</div></div>';\n html += '<div class=\"settings-item\"><div><div class=\"settings-label\">向量检索</div></div><div class=\"settings-value\">SQLite embedding</div></div>';\n html += '</div>';\n document.getElementById(\"system-status\").innerHTML = html;\n }\n\n function renderRecentMessages(items) {\n var el = document.getElementById(\"recent-messages\");\n if (!items || items.length === 0) {\n el.innerHTML = '<div class=\"empty-state\">还没有消息。启动 Gateway 后,群聊文本会进入本地 RAG 索引。</div>';\n return;\n }\n var html = '<div class=\"message-list\">';\n for (var i = 0; i < Math.min(items.length, 5); i++) {\n var item = items[i];\n html += '<div class=\"message-card\"><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><div class=\"message-text\">' + escapeHtml(item.text) + '</div></div>';\n }\n html += '</div>';\n el.innerHTML = html;\n }\n\n function renderRecentEpisodes(items) {\n var el = document.getElementById(\"recent-episodes\");\n if (!items || items.length === 0) {\n el.innerHTML = '<div class=\"empty-state\">还没有会话记忆。</div>';\n return;\n }\n var html = '<div class=\"message-list\">';\n for (var i = 0; i < Math.min(items.length, 3); i++) {\n var item = items[i];\n html += '<div class=\"episode-card\"><div class=\"message-meta\">' +\n '<span>' + escapeHtml(formatDateTime(item.startedAt)) + \" - \" + escapeHtml(formatDateTime(item.endedAt)) + '</span>' +\n '<span>' + escapeHtml(item.messageCount) + ' 条消息</span>' +\n '</div><div class=\"message-text\">' + escapeHtml(item.summary) + '</div></div>';\n }\n html += '</div>';\n el.innerHTML = html;\n }\n\n function renderMessagesView() {\n var el = document.getElementById(\"messages-list\");\n if (!allMessages || allMessages.length === 0) {\n el.innerHTML = '<div class=\"empty-state\">还没有消息。</div>';\n return;\n }\n var searchInput = document.getElementById(\"message-search\");\n var searchTerm = searchInput ? searchInput.value.toLowerCase() : \"\";\n var filtered = searchTerm ? allMessages.filter(function(m) { return (m.text || \"\").toLowerCase().indexOf(searchTerm) !== -1; }) : allMessages;\n if (filtered.length === 0) {\n el.innerHTML = '<div class=\"empty-state\">没有找到匹配的消息。</div>';\n return;\n }\n var html = '<div class=\"message-list\">';\n for (var i = 0; i < Math.min(filtered.length, 50); i++) {\n var item = filtered[i];\n html += '<div class=\"message-card\"><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><div class=\"message-text\" style=\"-webkit-line-clamp:4;\">' + escapeHtml(item.text) + '</div></div>';\n }\n html += '</div>';\n if (filtered.length > 50) {\n html += '<div style=\"text-align:center;padding:var(--space-md);color:var(--text-muted);font-size:13px;\">还有 ' + (filtered.length - 50) + ' 条消息...</div>';\n }\n el.innerHTML = html;\n }\n\n function filterMessages() { renderMessagesView(); }\n\n function renderEpisodesView() {\n var el = document.getElementById(\"episodes-list\");\n if (!allEpisodes || allEpisodes.length === 0) {\n el.innerHTML = '<div class=\"empty-state\">还没有会话记忆。</div>';\n return;\n }\n var html = '<div class=\"timeline\">';\n for (var i = 0; i < allEpisodes.length; i++) {\n var item = allEpisodes[i];\n html += '<div class=\"timeline-item\"><div class=\"timeline-date\">' + escapeHtml(formatDateTime(item.startedAt)) + \" - \" + escapeHtml(formatDateTime(item.endedAt)) + \" \\u00b7 \" + escapeHtml(item.messageCount) + ' \\u6761\\u6d88\\u606f</div><div class=\"timeline-content\">' + escapeHtml(item.summary) + '</div></div>';\n }\n html += '</div>';\n el.innerHTML = html;\n }\n\n function renderFilesView() {\n var el = document.getElementById(\"files-list\");\n if (!allFiles || allFiles.length === 0) {\n el.innerHTML = '<div class=\"content-panel glass\"><div class=\"empty-state\">还没有文件。运行 <code>chattercatcher files add <path...></code> \\u5bfc\\u5165\\u6587\\u4ef6\\u3002</div></div>';\n return;\n }\n var html = '<div class=\"grid-2\">';\n for (var i = 0; i < allFiles.length; i++) {\n var item = allFiles[i];\n html += '<div class=\"file-card glass\"><div class=\"file-icon\">' +\n '<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"white\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>' +\n '</div><div style=\"font-weight:600;margin-bottom:4px;\">' + escapeHtml(item.fileName) + '</div>' +\n '<div style=\"font-size:13px;color:var(--text-muted);margin-bottom:4px;\" class=\"truncate\">' + escapeHtml(item.storedPath) + '</div>' +\n '<div style=\"display:flex;gap:var(--space-sm);\"><span class=\"tag\">' + escapeHtml(item.parser || \"unknown\") + '</span><span class=\"tag\">' + escapeHtml(item.characters) + ' \\u5b57\\u7b26</span></div></div>';\n }\n html += '</div>';\n el.innerHTML = html;\n }\n\n function renderTasksView() {\n var activeTab = document.querySelector(\".tab.active\");\n var tab = activeTab ? activeTab.dataset.tab : \"file-jobs\";\n if (tab === \"file-jobs\") renderFileJobs();\n else renderCronJobs();\n }\n\n function renderFileJobs() {\n var el = document.getElementById(\"file-jobs-list\");\n if (!allFileJobs || allFileJobs.length === 0) {\n el.innerHTML = '<div class=\"empty-state\">还没有文件解析任务。</div>';\n return;\n }\n var html = '<table class=\"data-table\"><thead><tr><th>文件</th><th>状态</th><th>信息</th></tr></thead><tbody>';\n for (var i = 0; i < allFileJobs.length; i++) {\n var item = allFileJobs[i];\n var tagClass = item.status === 'indexed' ? 'tag-success' : item.status === 'failed' ? 'tag-error' : 'tag-warning';\n html += '<tr><td><div style=\"font-weight:500;\">' + escapeHtml(item.fileName) + '</div><div style=\"font-size:12px;color:var(--text-muted);\" class=\"truncate\">' + escapeHtml(item.storedPath || item.id) + '</div></td>' +\n '<td><span class=\"tag ' + tagClass + '\">' + escapeHtml(item.status) + '</span></td>' +\n '<td style=\"font-size:13px;color:var(--text-muted);\">' + escapeHtml(item.error || \"\") + '</td></tr>';\n }\n html += '</tbody></table>';\n el.innerHTML = html;\n }\n\n function renderCronJobs() {\n var el = document.getElementById(\"cron-jobs-list\");\n if (!allCronJobs || allCronJobs.length === 0) {\n el.innerHTML = '<div class=\"empty-state\">还没有定时任务。</div>';\n return;\n }\n var html = '<table class=\"data-table\"><thead><tr><th>任务</th><th>状态</th><th>操作</th></tr></thead><tbody>';\n for (var i = 0; i < allCronJobs.length; i++) {\n var item = allCronJobs[i];\n var tagClass = item.status === 'active' ? 'tag-success' : 'tag-warning';\n html += '<tr><td><div style=\"font-weight:500;\">' + escapeHtml(item.schedule) + '</div>' +\n '<div style=\"font-size:13px;color:var(--text-muted);\" class=\"truncate-2\">' + escapeHtml(item.prompt) + '</div>' +\n '<div style=\"font-size:12px;color:var(--text-muted);\">\\u4e0b\\u6b21: ' + escapeHtml(formatDateTime(item.nextRunAt)) + '</div>' +\n (item.lastError ? '<div style=\"font-size:12px;color:var(--danger);margin-top:4px;\">' + escapeHtml(item.lastError) + '</div>' : '') +\n '</td><td><span class=\"tag ' + tagClass + '\">' + escapeHtml(item.status) + '</span></td><td>' +\n (item.status === \"active\" ? '<button class=\"btn btn-sm btn-danger\" data-delete-cron-job=\"' + escapeHtml(item.id) + '\">\\u5220\\u9664</button>' : '-') +\n '</td></tr>';\n }\n html += '</tbody></table>';\n el.innerHTML = html;\n }\n\n function renderQaLogsView() {\n var el = document.getElementById(\"qa-logs-list\");\n if (!allQaLogs || allQaLogs.length === 0) {\n el.innerHTML = '<div class=\"empty-state\">还没有问答日志。</div>';\n return;\n }\n var html = '';\n for (var i = 0; i < allQaLogs.length; i++) {\n var item = allQaLogs[i];\n var citationCount = Array.isArray(item.citations) ? item.citations.length : 0;\n var statusClass = item.status === 'success' ? 'tag-success' : 'tag-warning';\n html += '<div class=\"qa-card\"><div class=\"message-meta\" style=\"margin-bottom:var(--space-sm);\">' +\n '<span>' + escapeHtml(formatDateTime(item.createdAt)) + '</span>' +\n '<span class=\"tag ' + statusClass + '\">' + escapeHtml(item.status) + '</span>' +\n '<span>' + citationCount + ' \\u6761\\u5f15\\u7528</span></div>' +\n '<div class=\"qa-question\">' + escapeHtml(item.question) + '</div>' +\n '<div class=\"qa-answer\">' + escapeHtml(item.answer) + '</div></div>';\n }\n el.innerHTML = html;\n }\n\n function renderSettings(status) {\n var el = document.getElementById(\"settings-config\");\n var html = '<h3 style=\"font-size:16px;font-weight:600;margin-bottom:var(--space-md);\">\\u7cfb\\u7edf\\u914d\\u7f6e</h3>';\n html += '<div style=\"display:flex;flex-direction:column;\">';\n html += '<div class=\"settings-item\"><div><div class=\"settings-label\">Web UI</div><div class=\"settings-desc\">' + escapeHtml((status.web && status.web.host ? status.web.host : \"127.0.0.1\") + \":\" + (status.web && status.web.port ? status.web.port : \"3878\")) + '</div></div></div>';\n html += '<div class=\"settings-item\"><div><div class=\"settings-label\">Gateway</div><div class=\"settings-desc\">' + (status.gateway.configured ? \"\\u5df2\\u914d\\u7f6e\" : \"\\u672a\\u914d\\u7f6e\") + '</div></div></div>';\n html += '<div class=\"settings-item\"><div><div class=\"settings-label\">RAG \\u6a21\\u5f0f</div><div class=\"settings-desc\">\\u5f3a\\u5236\\u5148\\u68c0\\u7d22\\u8bc1\\u636e\\uff0c\\u7981\\u6b62\\u5168\\u91cf\\u4e0a\\u4e0b\\u6587\\u5806\\u53e0</div></div></div>';\n html += '<div class=\"settings-item\"><div><div class=\"settings-label\">\\u6570\\u636e\\u76ee\\u5f55</div><div class=\"settings-desc\">SQLite + \\u672c\\u5730\\u6587\\u4ef6</div></div></div>';\n html += '</div>';\n el.innerHTML = html;\n }\n\n async function loadSection(path, setter) {\n try { setter(await fetchJson(path)); }\n catch (error) { console.error(\"\\u52a0\\u8f7d\\u5931\\u8d25:\", path, error); }\n }\n\n async function load() {\n await loadSection(\"/api/status\", function(data) {\n statusData = data;\n renderMetrics(data);\n renderSystemStatus(data);\n renderSettings(data);\n });\n await loadSection(\"/api/messages/recent?limit=50\", function(data) { allMessages = data.items || []; renderRecentMessages(allMessages); });\n await loadSection(\"/api/episodes?limit=20\", function(data) { allEpisodes = data.items || []; renderRecentEpisodes(allEpisodes); });\n await loadSection(\"/api/files\", function(data) { allFiles = data.items || []; });\n await loadSection(\"/api/file-jobs\", function(data) { allFileJobs = data.items || []; });\n await loadSection(\"/api/qa-logs?limit=20\", function(data) { allQaLogs = data.items || []; });\n await loadSection(\"/api/cron-jobs\", function(data) { allCronJobs = data.items || []; });\n if (currentView === \"messages\") renderMessagesView();\n if (currentView === \"episodes\") renderEpisodesView();\n if (currentView === \"files\") renderFilesView();\n if (currentView === \"tasks\") renderTasksView();\n if (currentView === \"qa-logs\") renderQaLogsView();\n }\n\n async function processNow() {\n var btn = document.getElementById(\"btn-process-messages\");\n if (btn) { btn.disabled = true; }\n showToast(\"\\u6b63\\u5728\\u5904\\u7406\\u6d88\\u606f\\u7d22\\u5f15...\", \"info\");\n try {\n var result = await postJson(\"/api/process/messages\");\n if (result.status === \"skipped\") { showToast(result.reason, \"warning\"); }\n else { showToast(\"\\u5904\\u7406\\u5b8c\\u6210\\uff1achunks=\" + result.chunks + \", vectors=\" + result.vectors, \"success\"); }\n await load();\n } catch (error) {\n showToast(error instanceof Error ? error.message : String(error), \"error\");\n } finally {\n if (btn) { btn.disabled = false; }\n }\n }\n\n document.addEventListener(\"click\", async function(event) {\n var target = event.target;\n if (!(target instanceof HTMLElement)) return;\n var id = target.dataset.deleteCronJob;\n if (!id) return;\n target.disabled = true;\n showToast(\"\\u6b63\\u5728\\u5220\\u9664\\u5b9a\\u65f6\\u4efb\\u52a1...\", \"info\");\n try {\n var result = await deleteJson(\"/api/cron-jobs/\" + encodeURIComponent(id));\n showToast(result.ok ? \"\\u5b9a\\u65f6\\u4efb\\u52a1\\u5df2\\u5220\\u9664\" : (result.message || \"\\u5220\\u9664\\u5931\\u8d25\"), result.ok ? \"success\" : \"error\");\n await load();\n } catch (error) {\n showToast(error instanceof Error ? error.message : String(error), \"error\");\n }\n });\n\n void load();\n setInterval(function() { if (document.visibilityState === \"visible\") void load(); }, 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 getWebActionCookie(token: string): string {\n return `chattercatcher_web_token=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Strict`;\n}\n\nfunction parseCookies(header: string | string[] | undefined): Record<string, string> {\n const value = Array.isArray(header) ? header.join(\"; \") : header;\n if (!value) return {};\n const cookies: Record<string, string> = {};\n for (const part of value.split(\";\")) {\n const [rawName, ...rawValue] = part.trim().split(\"=\");\n if (!rawName || rawValue.length === 0) continue;\n cookies[rawName] = decodeURIComponent(rawValue.join(\"=\"));\n }\n return cookies;\n}\n\nfunction isAuthorizedWebAction(request: { headers: Record<string, string | string[] | undefined> }, token: string): boolean {\n return parseCookies(request.headers.cookie).chattercatcher_web_token === token;\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 reply.header(\"set-cookie\", getWebActionCookie(webActionToken));\n return buildHtml();\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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":["path","os","path","path","fs","path","path","fs","deleted","fs","path","path","fs","fs","crypto","path","fs","path","fs","path","path","fs","path","fs","crypto","nowIso","crypto","crypto","nowIso","stableId","crypto","escapeFtsQuery","buildScopeWhere","toEvidenceSource","buildScopeWhere","fs","fs","path","path","fs","fs","path","lark","path","parseCronSchedule","matchesParsedSchedule","parseMinuteField","parseExactOrWildcardField","parseExactNumber","toEvidenceSource","crypto","nowIso","stableId","path","crypto","fs","fs","path","asObject","fileKey","lark","fs","path","path","fs","crypto","fs","path","fs","path","path","crypto","fs","crypto","crypto"]}
|