/** * Per-session chat event stream — tails an agent's transcript file and * emits a stable `ChatEvent` shape the mobile chat panel renders. * * The agent-specific bits (where the file lives, how to translate * records into ChatEvents) live in transcript-adapters.ts. This module * owns the generic tail loop: byte-offset replay, fs.watch + polling * fallback, size-delta gap detection, per-session backpressure * coordination. * * Wire shape is co-located here so the relay client can `satisfies * ChatEvent` against the emitted payloads without an extra dep. The * shape is intentionally NOT exported to `@loopsy/protocol` yet — once * we trust it across all four adapters, the version gets carved out. */ import { EventEmitter } from 'node:events'; import { type TranscriptAdapter } from './transcript-adapters.js'; export type ChatBlock = { type: 'text'; text: string; } | { type: 'thinking'; text: string; } | { type: 'tool_use'; id: string; name: string; input: unknown; } | { type: 'tool_result'; toolUseId: string; content: unknown; isError: boolean; truncated?: boolean; }; export type ChatErrorCode = 'tail-gap' | 'jsonl-missing' | 'schema-unknown' | 'session-ended' | 'project-dir-missing'; export type ChatEvent = { v: 1; kind: 'capability'; chat: 'available' | 'unavailable'; reason?: string; } | { v: 1; kind: 'turn-start'; turnId: string; role: 'user' | 'assistant'; ts?: string; messageId?: string; } | { v: 1; kind: 'block'; turnId: string; messageId: string; index: number; block: ChatBlock; } | { v: 1; kind: 'turn-end'; turnId: string; stopReason?: string; } | { v: 1; kind: 'error'; code: ChatErrorCode; message: string; }; export interface ChatEventStreamOptions { /** Agent-specific adapter (knows where the transcript file lives + how to translate). */ adapter: TranscriptAdapter; /** The cwd the PTY was launched from. */ cwd: string; /** Optional sessionId hint — used by adapters that support exact lookup. */ sessionId?: string; /** * Loopsy session id. Adapters use this to re-check tracker maps on * every poll iteration, so a tracker entry that gets persisted by * the async discovery service mid-poll is picked up immediately. */ loopsySessionId?: string; /** * Epoch-ms when the owning PTY was spawned. Used to bind to the right * file when multiple sessions share a directory. */ ptySpawnedAtMs?: number; /** Polling interval used as fs.watch backup. Default 500ms. */ pollMs?: number; /** Byte offset to start tailing from. Default 0 (full replay). */ startByteOffset?: number; } /** Back-compat re-export so external callers don't have to learn the new module. */ export declare function encodeCwdToProjectDir(cwd: string): string; export declare class ChatEventStream extends EventEmitter { private adapter; private cwd; private sessionId?; private loopsySessionId?; private ptySpawnedAtMs?; private pollMs; private startByteOffset?; private filePath; private watcher; private pollTimer; private nextByteOffset; private pendingBuffer; private translator; private stopped; private reading; constructor(options: ChatEventStreamOptions); start(): Promise; stop(): void; private readNew; private emitEvent; } //# sourceMappingURL=chat-event-stream.d.ts.map