import { EventEmitter } from 'node:events'; import type { LogEntry } from '../core/logger.js'; export interface LogReaderOptions { pollIntervalMs?: number; } export interface LogReaderEvents { entries: (entries: LogEntry[]) => void; error: (err: Error) => void; } /** * 解析 rotation 条目指向的目标文件,做范围校验 + 日期白名单。 * * CRITICAL-1: `meta.new_file` 来自磁盘 JSONL 行(任何能写入日志目录的进程均可构造), * 必须确保 resolve 后仍落在 logDir **直接子文件**(非子目录、非外部路径),且文件名严格为 * `YYYY-MM-DD.jsonl`,防止路径遍历/任意文件读取。合法返回 logDir 内的绝对路径;非法返回 null。 */ export declare function resolveRotationTarget(logDir: string, newFile: string): string | null; declare class LogReader extends EventEmitter { private logDir; private pollIntervalMs; private byteOffset; private currentFile; private skippedCount; private pendingTail; private watcher; private pollTimer; private stopped; private fd; private fdPath; private lastCheckMs; private static readonly CHECK_DEBOUNCE_MS; constructor(logDir: string, opts?: LogReaderOptions); start(): Promise; stop(): void; getSkippedCount(): number; getCurrentFile(): string; private getTodayFile; private startWatch; private startPolling; private checkForNewData; /** * Get or create a cached file descriptor for the given path. * Reopens when the path changes (e.g. date rotation). */ private ensureFd; /** * 用 readSync + Buffer 从 byteOffset 读取增量内容。 * I-03: 用 pendingTail 缓冲不完整尾部,防止 UTF-8 多字节截断。 */ private readFromOffset; } export { LogReader };