import type { InputSource } from "../tui/input/types.js"; import type { OutputTarget } from "../tui/output/types.js"; import { type ParseResult } from "../statelog/parse.js"; import type { CommentsStore } from "../runDirectory/commentsStore.js"; import type { Anchor } from "../runDirectory/comments.js"; import { ViewerThresholds } from "./thresholds.js"; import { ScreenHost } from "./screenHost.js"; import { type ViewerBinding } from "./keymap.js"; import type { TreeNode } from "./types.js"; export type RunViewerOpts = { parsed?: ParseResult; input: InputSource; output: OutputTarget; viewport: { rows: number; cols: number; }; followPath?: string; initialFollow?: boolean; followIntervalMs?: number; embedded?: boolean; thresholds?: Partial; extract?: { sourcePath: string; }; traceAnnotations?: Record; focusTraceId?: string; comments?: CommentsStore; session?: { dir: string; }; contextWindowOf?: (model: string) => number | undefined; }; /** How the viewer ended. `"noSuchTrace"` is a `--trace-id` that named no * single trace, which the CLI turns into exit code 1. */ export type ViewerResolution = "quit" | "back" | "noSuchTrace"; export declare function runViewer(opts: RunViewerOpts): Promise; /** * Where the cursor goes when its trace is not in a fresh forest. A trace asked * for by id under `--follow` may not have started yet: while the viewer is * still on it, keep waiting for it rather than jumping to the newest trace on * the first poll. Once the person moves to another trace, the usual rule * applies. */ export declare function fallbackTraceId(roots: TreeNode[], currentTraceId: string, focusTraceId: string | undefined): string; /** The one trace a full id or unique prefix names; undefined when none or * several match. The same rule the CLI applies (`matchTraceId`). */ export declare function resolveTracePrefix(roots: TreeNode[], idOrPrefix: string): string | undefined; /** The trace that started last; the last root when none has a start time. */ export declare function mostRecentTraceId(roots: TreeNode[]): string; type ShellControls = { typing: boolean; quit: () => void; escape: () => void; }; type ShellBinding = ViewerBinding & { control?: boolean; }; /** Control bindings also run over help and narrow-terminal messages. */ export declare function viewerShellBindings(host: ScreenHost, roots: () => TreeNode[], toggleFollow: () => void, openPicker: () => void, controls?: ShellControls, comments?: CommentsSource): ShellBinding[]; /** * The follow watcher. It owns the one line reader for the file and the log * parsed from it, alive for the whole session. The reader's first read IS the * boot read, so nothing can land in a gap between a separate boot read and the * watcher start, and `f` toggles POLLING only: the reader and the log persist. * Each poll parses only the lines appended since the last one and hands the * shell a new log with the old events in it. A file that SHRANK was rotated or * truncated: start over from offset 0 with an empty log; view setData * cursor-fallback absorbs it. * * A file opened without `--follow` is treated as finished at boot, so its * partial last line goes through the reader's `finish()`. Polls show complete * lines only, since a live writer may be in the middle of one; the reader * keeps that partial line, so a later poll can complete it. * * @internal Exported for its test. */ export declare function makeFollowWatcher(opts: RunViewerOpts): { /** The log as the boot read found it. */ boot: ParseResult; /** `onLog` gets the whole log after every poll that changed it. `onTick` * runs on every poll, grown or not: the comments file can change while * the log is idle. */ start(onLog: (log: ParseResult) => void, onTick?: () => void): void; stop(): void; }; /** * The one piece of the viewer that reads or writes comments. `refresh` folds * the store into marks and hands them to every view; `add` writes and then * refreshes. The `c` key, the follow tick, and the session's `reload` all * come through here, so the sequence exists once. */ export type CommentsSource = { available: boolean; refresh(): void; /** For a poller: re-read only when the file's size changed. */ refreshIfChanged(): void; /** Everything a comment needs, taken while the row is still the one the * person is on. Undefined with no row. */ targetFor(focus: { traceId: string; rowId: string | undefined; }): CommentTarget | undefined; add(target: CommentTarget, text: string): void; toggleAgentNotes(): void; }; export type CommentTarget = { traceId: string; rowId: string; anchor: Anchor; }; export type { TreeNode };