import { type VisualDiagnostic, type VisualEvent, type VisualFreezeReason, type VisualHandoff, type VisualLifecycle, type VisualSessionRequest, type VisualSessionStarted, type VisualStatus, type VisualTerminationReason } from "./protocol.js"; import { type TerminalEventDependencies, type VisualSessionPaths } from "./session-store.js"; export { VISUAL_SERVER_DOCUMENT } from "./workspace-model.js"; import type { VisualRenderedModel, VisualServerFrame, VisualSessionSnapshot, VisualTranscriptRecord } from "./wire.js"; export type { VisualRenderedModel, VisualServerFrame, VisualSessionSnapshot, VisualTranscriptRecord, }; /** * Returned on every response. The policy admits no external origin at all, so * neither a compromised model nor a hostile chat message can reach the network, * and the page can be neither framed nor used as a base for relative fetches. */ export declare const VISUAL_BROWSER_HEADERS: { readonly "Referrer-Policy": "no-referrer"; readonly "X-Content-Type-Options": "nosniff"; readonly "Cache-Control": "no-store"; }; /** * This session's content policy. Every source stays `'self'`; the one addition * is a per-session nonce for inline style, because the diagram renderer injects * its own stylesheets at runtime. A nonce admits exactly the styles this * application emits, where `'unsafe-inline'` would admit anyone's. */ export declare const visualContentSecurityPolicy: (styleNonce: string) => string; export declare const VISUAL_SERVER_LIMITS: { /** WebSocket frame ceiling: one 64 KiB chat message plus its envelope. */ readonly browserFrameBytes: number; /** Agent request body ceiling: one 5 MiB candidate model plus its envelope. */ readonly agentBodyBytes: number; /** Agent stop requests carry a termination reason and nothing else. */ readonly agentControlBytes: 4096; /** How long one agent long poll waits before answering "still idle". */ readonly agentPollMs: 30000; /** Browser sockets one session serves at once. */ readonly browserConnections: 8; }; export declare const VISUAL_SOCKET_PATH = "/socket"; export type VisualEventDelivery = { readonly waiting: false; readonly event: VisualEvent; readonly lastSequence: number; readonly pendingEvents: number; } | { readonly waiting: true; readonly lastSequence: number; readonly pendingEvents: number; }; export type VisualResponseAcceptance = { readonly accepted: true; readonly duplicate: boolean; readonly lastSequence: number; readonly diagnostics: readonly VisualDiagnostic[]; } | { readonly accepted: false; readonly diagnostics: readonly VisualDiagnostic[]; }; export interface VisualServerClosed { readonly reason: VisualTerminationReason; readonly alreadyStopped: boolean; readonly handoff: VisualHandoff | undefined; } export interface VisualServerOptions { readonly request: VisualSessionRequest; /** Directory that holds one directory per live session. */ readonly baseDir: string; /** Working directory for resolving .yarramate/workspace.yaml and projections. */ readonly cwd: string; /** Root of the self-contained browser application. */ readonly assetRoot?: string; readonly now?: () => Date; readonly randomBytes?: (size: number) => Buffer; readonly agentPollMs?: number; /** Whether the handoff a stop returns carries the raw transcript. */ readonly includeTranscript?: boolean; /** * Arms the browser reconnect grace, and answers with the cancellation of the * window it armed. Injected so the exact window a session waits is * observable, and so it can elapse without waiting out five real minutes. */ readonly schedule?: (task: () => Promise, ms: number) => () => void; } export interface VisualServerHandle { readonly started: VisualSessionStarted; readonly closed: Promise; status(): VisualStatus; stop(reason: VisualTerminationReason): Promise; } /** * The runtime half of one live session: everything the terminal transition * mutates, and the hooks it needs to quiet the rest down first. * * `lifecycle` is the whole admission gate — only a `running` session takes a * browser frame or an agent response — so freezing input is one assignment * rather than a second flag that could disagree with it. */ export interface ActiveVisualSession { readonly paths: VisualSessionPaths; lifecycle: VisualLifecycle; /** Records why the browser stopped being able to speak. */ readonly freeze: (reason: VisualFreezeReason) => void; /** Settles work already admitted, so recovery reads a quiet journal. */ readonly quiesce: () => Promise; readonly terminalEvent: TerminalEventDependencies; /** The transition in flight, so concurrent causes converge on one run. */ terminating: Promise | undefined; /** What this session ended with, once it has ended. */ handoff: VisualHandoff | undefined; } /** * Takes one session terminal, whatever caused it: a reviewer's End, a child * that failed, a browser that never came back, a cancelling main agent, or a * runtime shutting itself down. * * The order is the invariant. Input freezes before anything is read; work * already admitted is still allowed to finish; exactly one terminal event is * journaled; and the handoff is recovered without deleting anything, so * `stop` — and only `stop` — is what removes the session directory. */ export declare const terminateVisualSession: (session: ActiveVisualSession, reason: VisualTerminationReason) => Promise; /** * Serves one authenticated visual session over loopback. The handle owns the * session directory it created: `stop` recovers the handoff before deleting it, * and answers every later call with that same outcome. */ export declare const startVisualServer: (options: VisualServerOptions) => Promise;