import { ConversationCache } from "./conversation-cache"; import type { CacheIntegrityMonitor } from "./services/cache-integrity/cacheIntegrityMonitor"; import type { ConversationWatcher } from "./services/conversations/conversationWatcher"; import type { SessionActivity, SessionResponse } from "./types"; import type { WSHub } from "./ws-hub"; export declare const EXTERNAL_TAIL_RECENCY_MS = 120000; export declare const EXTERNAL_TAIL_MAX = 32; export declare const EXTERNAL_TAIL_IDLE_MS = 300000; export declare const EXTERNAL_ACTIVE_WRITING_MS = 30000; export type ExternalTailEntry = { conversationId: string; lastActivityAt: number; }; /** * Everything ExternalTailManager reads from the server. Nullable collaborators * are thunks rather than values because they are opened during listen() and * rebound by the integrity monitor's reset-and-rescan — the same reason * ApiDeps passes `cache: () => ConversationCache | null`. * * `tails` and `sessionFileMap` are the server's own Maps, held by reference: * the server still owns them as instance fields (both are reached directly * elsewhere in server.ts, and the tests read `externalTails` off the server), * this class only drives them. */ export type ExternalTailManagerDeps = { tails: Map; sessionFileMap: Map; fileWatcher: ConversationWatcher; wsHub: WSHub; cache: () => ConversationCache | null; cacheMonitor: () => CacheIntegrityMonitor | null; broadcastConversationLines: (sessionId: string, lines: string[], seqs?: (number | null)[] | null) => void; }; /** * Owns the live tails on JSONLs no PTY session is writing: when one attaches, * when it is evicted or swept, the inferred activity it feeds into session * responses, and the transcript lines it pushes. * * Extracted from StreamerServer so external-tail work stops editing the server * file, continuing the split in docs/plans/2026-07-12-server-ts-split.md. */ export declare class ExternalTailManager { private deps; private log; constructor(deps: ExternalTailManagerDeps); private get externalTails(); /** True when a managed (PTY) session owns the tail for this canonical path. */ isManagedTailPath(key: string): boolean; /** * Attach a live tail to a JSONL nobody is tailing yet, when it was touched * recently enough to look actively written by an external agent. Capped at * EXTERNAL_TAIL_MAX with LRU eviction. */ maybeAttachExternalTail(filePath: string): void; /** Stop tailing an external file and drop its bookkeeping. */ detachExternalTail(key: string): void; /** * Shared unlink path for the per-file watcher and the directory watcher. * Detaches any external tail and drops the cache row (unless an integrity * alert is freezing deletes). */ handleJsonlDeleted(filePath: string): void; /** Make room for one more tail by evicting the least recently active ones. */ evictExternalTailsIfNeeded(): void; /** * INFERRED activity for an externally-owned conversation, derived purely from * how recently its JSONL grew (the external tail's bookkeeping). Returns * undefined when we hold no tail for it, so a session we know nothing about * reports no activity rather than a fabricated "quiet". * * This can never distinguish a generating agent from one blocked on a * permission gate — gates render on the PTY screen and never reach the JSONL — * which is why it is a separate field and not folded into `status`. */ externalActivityFor(conversationId: string, now?: number): SessionActivity | undefined; /** Attach inferred `activity` to externally-owned sessions in a response set. */ withExternalActivity(sessions: readonly SessionResponse[]): readonly SessionResponse[]; /** Detach external tails idle past EXTERNAL_TAIL_IDLE_MS. */ sweepIdleExternalTails(now?: number): void; /** * Push appended lines from an externally-owned conversation. Reuses the exact * conversation_events / conversation_event shapes mobile already consumes, * keyed by the conversation UUID — an external session has no PTY, so it must * never produce terminal_output / terminal_replay / session_ready, and never a * session_update whose session.id is a conversation UUID (that would mint a * phantom session row in the mobile cache). Question cards are likewise never * derived here: with no PTY there is nothing that could deliver an answer. */ broadcastExternalTailLines(filePath: string, lines: string[], seqs?: (number | null)[] | null): void; } //# sourceMappingURL=external-tails.d.ts.map