/**
 * WizardStore — Nanostore-backed reactive store for the TUI.
 * React components subscribe via useSyncExternalStore.
 *
 * Navigation is delegated to WizardRouter.
 * The active screen is derived from session state — not imperatively set.
 * Overlays (outage, etc.) are the only imperative navigation.
 *
 * All session mutations that affect screen resolution go through
 * explicit setters so emitChange() is always called.
 */
import { TaskStatus, type EventPlanDecision } from '../wizard-ui.js';
import { type WizardSession, type OutroData, type DiscoveredFeature, type CloudRegion, type RetryState, type PostAgentStep } from '../../lib/wizard-session.js';
import { AdditionalFeature, McpOutcome, SlackOutcome, RunPhase, PostAgentStepStatus } from './session-constants.js';
import { WizardRouter, type ScreenName, Screen, Overlay, Flow } from './router.js';
import type { JourneyStepId, JourneyStatus } from '../../lib/journey-state.js';
export { TaskStatus, Screen, Overlay, Flow, RunPhase, McpOutcome, SlackOutcome, PostAgentStepStatus, };
export type { ScreenName, OutroData, WizardSession, RetryState, PostAgentStep };
export interface TaskItem {
    label: string;
    activeForm?: string;
    status: TaskStatus;
    /** Legacy compat */
    done: boolean;
}
export interface PlannedEvent {
    name: string;
    description: string;
}
/**
 * One row of the FileWritesPanel — bound to a single
 * `recordFileChangePlanned` → `recordFileChangeApplied` pair from the
 * inner agent. `path` is keyed on the absolute path the inner agent
 * passed; relativization for display happens at render time so the
 * panel renders correctly even if the same path comes through twice
 * (Edit + verify pass).
 */
export interface FileWriteEntry {
    /** Absolute path, as the inner agent's tool received it. */
    path: string;
    operation: 'create' | 'modify' | 'delete';
    /** 'planned' once PreToolUse fires; flips to 'applied' or 'failed' on PostToolUse. */
    status: 'planned' | 'applied' | 'failed';
    /** Wall-clock ms when the planned event arrived. Drives the elapsed timer. */
    startedAt: number;
    /** Wall-clock ms when the applied/failed event arrived. */
    completedAt?: number;
    /** Byte size from the inner agent's tool input, when available (Write only). */
    bytes?: number;
}
export type PendingPrompt = {
    kind: 'confirm';
    message: string;
    resolve: (value: boolean) => void;
} | {
    kind: 'choice';
    message: string;
    options: string[];
    resolve: (value: string) => void;
} | {
    kind: 'event-plan';
    events: PlannedEvent[];
    resolve: (value: EventPlanDecision) => void;
};
export declare class WizardStore {
    private $session;
    private $statusMessages;
    private $tasks;
    private $eventPlan;
    /**
     * Live list of file writes the inner agent has issued during the run.
     * Bounded to the most recent MAX_FILE_WRITES entries (FIFO eviction)
     * so a runaway inner agent that touches hundreds of files doesn't blow
     * up the TUI's render budget.
     */
    private $fileWrites;
    /**
     * Monotonic counter of every file write initiated by the agent — incremented
     * once per logical write when planned (PreToolUse) or, for synthesized
     * entries only, when applied (PostToolUse without a prior planned event).
     * Unlike `$fileWrites.length` (which caps at MAX_FILE_WRITES), this keeps
     * climbing past the cap so coaching/heartbeat signals that key off it
     * don't stall after the 50th write.
     */
    private $fileWritesTotal;
    /**
     * Per-step journey status derived from deterministic tool-call signals
     * (see `src/lib/journey-state.ts`). This is the source of truth for the
     * canonical 5-step progress UI; `syncTodos` only updates each step's
     * `activeForm` flavor text. Initialised on first `applyJourneyTransition`
     * call — until then each step is treated as `pending`.
     */
    private $derivedJourney;
    /**
     * Latest non-empty `activeForm` the agent has emitted via `TodoWrite`,
     * keyed by canonical step id. Refreshed every `syncTodos` call. The
     * renderer prefers this over the canonical `defaultActiveForm` so the
     * user sees the agent's current narration ("Installing project
     * dependencies") on the active row.
     */
    private $journeyActiveForms;
    private $version;
    /** True while the user is typing a slash command in the command bar. */
    private $commandMode;
    /** Transient feedback message shown in the command bar after a command runs. */
    private $commandFeedback;
    /** Error caught by the app error boundary — shown in ConsoleView. */
    private $screenError;
    private $screenErrorRetry;
    /** Tab id to switch to imperatively (e.g. from a slash command). */
    private $requestedTab;
    /** Last screen seen — used to detect screen transitions for analytics. */
    private _lastScreen;
    /**
     * Active timer for `setCommandFeedback`. Tracked on the instance so a
     * second feedback call (e.g. `/feedback` after `/whoami`) can cancel
     * the prior timer before scheduling its own — without the ref, the
     * older long timer can wipe a fresh feedback message and the user sees
     * the wrong text disappear.
     */
    private _feedbackTimer;
    /** Hooks run when transitioning onto a screen. */
    private _enterScreenHooks;
    version: string;
    /** Navigation router — resolves active screen from session state. */
    readonly router: WizardRouter;
    /**
     * Setup promise — IntroScreen resolves this when the user confirms.
     * bin.ts awaits it before calling runWizard.
     */
    private _resolveSetup;
    readonly setupComplete: Promise<void>;
    /** Pending confirmation or choice prompt from the agent. */
    private $pendingPrompt;
    /** Resolves when the user picks continue vs run wizard after Amplitude pre-detection. */
    private _preDetectedChoiceResolver;
    constructor(flow?: Flow);
    get session(): WizardSession;
    set session(value: WizardSession);
    get commandMode(): boolean;
    get commandFeedback(): string | null;
    get screenError(): Error | null;
    get screenErrorRetry(): number;
    get requestedTab(): string | null;
    get statusMessages(): string[];
    get tasks(): TaskItem[];
    get eventPlan(): PlannedEvent[];
    get fileWrites(): FileWriteEntry[];
    get fileWritesTotal(): number;
    get pendingPrompt(): PendingPrompt | null;
    /** Advances the flow past IntroScreen. Does not start the agent. */
    concludeIntro(): void;
    /**
     * Return to the welcome (Intro) screen. Used by Esc on RegionSelect and by
     * create-account EmailCapture when the user backs out before entering an account.
     * Does not clear region or framework — Continue from Intro re-resolves the
     * same downstream gates.
     */
    rewindIntro(): void;
    /**
     * Unblocks bin.ts via the setupComplete promise, signalling that the agent
     * can start. Called by bin.ts via onEnterScreen(Screen.Run) so it fires at
     * the right point in the flow — after auth, data check, and setup questions.
     */
    completeSetup(): void;
    setRunPhase(phase: RunPhase): void;
    /**
     * Seed the post-agent step queue once, before the first step starts.
     * Idempotent: a second call replaces the queue, so callers should only
     * invoke this at the post-agent boundary.
     */
    seedPostAgentSteps(steps: PostAgentStep[]): void;
    /**
     * Patch a single post-agent step by id. No-op if the id isn't seeded.
     * Callers transition `pending → in_progress → completed | skipped`;
     * we stamp `startedAt` on the in_progress transition so the
     * FinalizingPanel can compute per-step elapsed time without a separate
     * timer.
     */
    setPostAgentStep(id: string, patch: {
        status: PostAgentStepStatus;
        reason?: string;
    }): void;
    setCredentials(credentials: WizardSession['credentials']): void;
    /**
     * Update the cached user email used by `/whoami`. Required because nanostores'
     * map-based storage replaces the top-level session object on every `setKey`,
     * so closed-over `session` references in long-lived callbacks (e.g. the
     * re-auth watcher in bin.ts) become stale and direct mutation would land on
     * a discarded object.
     */
    setUserEmail(email: string | null): void;
    setApiKeyNotice(notice: string | null): void;
    setSelectedEnvName(name: string | null): void;
    setFrameworkConfig(integration: WizardSession['integration'], config: WizardSession['frameworkConfig']): void;
    setDetectionComplete(): void;
    /**
     * Mirror the full per-framework detection table onto the session.
     * Used by `/diagnostics` so users filing bug reports can see exactly
     * which detector returned what — even for frameworks that didn't
     * win. Set BEFORE `setDetectionComplete` so the screen never sees a
     * stale `[]` after the spinner flips off.
     */
    setDetectionResults(results: WizardSession['detectionResults']): void;
    setDetectedFramework(label: string): void;
    /**
     * Re-detection callback registered by bin.ts. Returning `null` from
     * `changeInstallDir` would cause it to no-op when the detector
     * isn't wired up (e.g. tests, classic mode), which we want — the
     * directory change still applies, we just skip the redetect.
     */
    private _redetect;
    /** AbortController for the active detection run, if any. */
    private _activeDetectionAbort;
    /**
     * Register the framework re-detection callback. Called once by
     * bin.ts during TUI bootstrap. Calling it more than once replaces
     * the previous callback (used by tests).
     */
    setFrameworkRedetector(redetect: (installDir: string, signal: AbortSignal) => Promise<unknown>): void;
    /**
     * Register an in-flight detection's AbortController so a subsequent
     * `changeInstallDir` can cancel it. bin.ts calls this once at boot
     * with the controller it created for the INITIAL detection — that
     * way a user who picks "Change directory" while the first
     * `Scanning…` spinner is still running cancels the initial run too,
     * not just later re-runs.
     *
     * Without this hook the initial detection's terminal
     * `setDetectionComplete()` could fire AFTER the directory swap has
     * reset state, briefly flashing stale framework results from the
     * old tree.
     */
    registerActiveDetection(controller: AbortController): void;
    /**
     * Swap the install directory and re-run framework detection.
     *
     * `newInstallDir` MUST be a resolved, existing absolute path —
     * the caller (PathInput in IntroScreen) handles `~` expansion and
     * validation so this method can stay pure-state-mutation.
     *
     * Effects:
     *   - cancels any in-flight detection
     *   - clears `frameworkConfig`, `integration`, `detectedFrameworkLabel`,
     *     `frameworkContext`, `detectionResults`, `discoveredFeatures`
     *   - flips `detectionComplete` back to false (spinner reappears)
     *   - clears the checkpoint-restore flag — checkpoint was for the
     *     OLD directory; resuming into a different tree would be wrong
     *   - kicks off a new detection run against the new directory
     */
    changeInstallDir(newInstallDir: string): void;
    setLoginUrl(url: string | null): void;
    setRegion(region: string): void;
    setSignupEmail(email: string): void;
    setSignupFullName(fullName: string): void;
    markEmailCaptureComplete(): void;
    markSignupTokensObtained(): void;
    acceptTermsOfService(): void;
    resetEmailCapture(): void;
    resetToS(): void;
    setAuthOnboardingPath(path: WizardSession['authOnboardingPath']): void;
    switchToLogin(): void;
    cancelWizard(reason: string): void;
    /**
     * Force the RegionSelect screen to re-appear (/region command).
     *
     * A mid-session region change is equivalent to logging out and logging back
     * in against the other data center: OAuth tokens are zone-scoped, and every
     * org/workspace/environment the user has picked lives in the old region.
     * Clear all of that so the Auth screen reappears once a new region is
     * picked, forcing a fresh login. OAuth tokens in the wizard session store
     * are kept per-zone and will be silently reused if the user already signed
     * into the target region previously.
     */
    setRegionForced(): void;
    /**
     * Reset session fields cleared by IntroScreen's "Start fresh" branch
     * after a checkpoint restore.
     *
     * Previously the screen did `store.session = { ...store.session, ... }`
     * — direct top-level reassignment goes through the `set session()`
     * setter, which calls `$session.set()` + `emitChange()`. That works
     * for `useSyncExternalStore` subscribers (everyone reads via
     * `$version`), BUT it bypasses the per-key change events that
     * nanostores `map.setKey()` emits. Any future code that subscribes
     * via `listenKeys(['integration'])` etc. would silently miss the
     * reset. Routing the same reset through individual `setKey` calls is
     * the idiomatic store pattern and keeps the contract uniform across
     * actions — every other reset (`setRegionForced`, etc.) does it this
     * way already.
     */
    resetForFreshStart(): void;
    /**
     * Rewind to Intro (Welcome). Used when Esc means “step back before region /
     * signup” — not for /region mid-session (`regionForced`), where tearing down
     * the spine would surprise users switching DC.
     */
    backToWelcome(): void;
    private _retryResolve;
    setScreenError(error: Error): void;
    clearScreenError(): void;
    /**
     * Returns a Promise that resolves the next time clearScreenError() is called
     * (i.e. the user presses R). Used by InkUI.setRunError to block until retry.
     */
    waitForRetry(): Promise<void>;
    /** Show a confirmation prompt. Resolves with true (yes) or false (no / skipped). */
    promptConfirm(message: string): Promise<boolean>;
    /** Show a multiple-choice prompt. Resolves with the chosen option or empty string if skipped. */
    promptChoice(message: string, options: string[]): Promise<string>;
    /** Resolve a confirm or choice pending prompt. */
    resolvePrompt(answer: boolean | string): void;
    /** Show an event-plan confirmation. Resolves when the user approves, skips, or gives feedback. */
    promptEventPlan(events: PlannedEvent[]): Promise<EventPlanDecision>;
    /** Resolve the pending event-plan prompt. */
    resolveEventPlan(decision: EventPlanDecision): void;
    /** Enter or exit slash command mode. */
    setCommandMode(active: boolean): void;
    showSnakeOverlay(): void;
    hideSnakeOverlay(): void;
    showMcpOverlay(): void;
    hideMcpOverlay(): void;
    showSlackOverlay(): void;
    hideSlackOverlay(): void;
    showLogoutOverlay(): void;
    hideLogoutOverlay(): void;
    showLoginOverlay(): void;
    hideLoginOverlay(): void;
    /** Update just the access token in credentials without triggering auth analytics. */
    updateAccessToken(accessToken: string): void;
    /** Update both access and id tokens (e.g. after a silent OAuth refresh). */
    updateTokens(accessToken: string, idToken: string): void;
    /**
     * Restore org/project/app session IDs that weren't populated at startup
     * (e.g. because the fire-and-forget fetchAmplitudeUser failed due to expired token).
     * Only updates fields that are provided.
     */
    restoreSessionIds(fields: {
        orgId?: string;
        orgName?: string;
        projectId?: string;
        projectName?: string;
        appId?: string | null;
    }): void;
    /** Request the TabContainer to switch to a tab by id. Clears after consumption. */
    setRequestedTab(id: string): void;
    clearRequestedTab(): void;
    /** Show a transient feedback message in the command bar. Clears after ms. */
    setCommandFeedback(message: string, ms?: number): void;
    setProjectHasData(value: boolean): void;
    setActivationLevel(level: 'none' | 'partial' | 'full'): void;
    /**
     * Mark this session as having a complete prior install detected on
     * disk — set when the Activation pre-flight finds all four local
     * signals (SDK dep, source import, ampli.json scope, event plan).
     * Pairs with `activationLevel = 'full'` to short-circuit Setup + Run
     * while keeping DataIngestionCheck running so the user can verify
     * events arrive after a re-deploy.
     */
    setLocalInstrumentationComplete(value: boolean): void;
    setSnippetConfigured(value: boolean): void;
    setDataIngestionConfirmed(): void;
    setChecklistDashboardUrl(url: string): void;
    setSignupMagicLinkUrl(url: string | null): void;
    setActivationOptionsComplete(): void;
    /**
     * Called from bin.ts when OAuth completes (browser redirect done).
     * Stores auth tokens + org list so AuthScreen can show the SUSI pickers.
     * Also sets region from the detected cloud zone.
     */
    setOAuthComplete(data: {
        accessToken: string;
        idToken: string;
        cloudRegion: CloudRegion | null;
        orgs: WizardSession['pendingOrgs'];
    }): void;
    /** Replace the cached org list (used by Start Over to pick up newly-created projects). */
    setPendingOrgs(orgs: WizardSession['pendingOrgs']): void;
    /**
     * Enter the create-project flow. Sets `session.createProject.pending = true`
     * so the router resolves to CreateProjectScreen.
     *
     * @param source which picker or invocation triggered creation
     * @param suggestedName optional pre-filled name (e.g. from /create-project <name> or --project-name)
     */
    startCreateProject(source: 'project' | 'environment' | 'slash' | 'cli-flag', suggestedName?: string | null): void;
    /** Exit the create-project flow without creating a project. */
    cancelCreateProject(): void;
    /**
     * Finish the create-project flow successfully. Clears the pending flag
     * — the caller is responsible for calling `setCredentials()` with the
     * returned apiKey so the rest of the auth flow stays consistent.
     */
    completeCreateProject(): void;
    /**
     * Called from AuthScreen when org + project selection changes.
     * Records org/project on the session, and (when `persist` is true)
     * writes the IDs to the project's ampli.json.
     *
     * Pass `persist: false` from synthesisers that only mirror existing state
     * (e.g. the auto-resolve effect that reflects values already loaded from
     * ampli.json) — those code paths shouldn't trigger fresh disk writes
     * during render. User-driven flows (picker selection, "start over",
     * "create project") leave `persist` at its default of `true` so the
     * config file stays in sync with what the user picked.
     */
    setOrgAndProject(org: {
        id: string;
        name: string;
    }, project: {
        id: string;
        name: string;
        environments?: Array<{
            rank: number;
            app: {
                id: string;
                apiKey?: string | null;
            } | null;
        }> | null;
    }, installDir: string, options?: {
        persist?: boolean;
    }): void;
    setServiceStatus(status: {
        description: string;
        statusPageUrl: string;
    } | null): void;
    setRetryState(state: RetryState | null): void;
    addDiscoveredFeature(feature: DiscoveredFeature): void;
    /**
     * Auto-enable every opt-in addon (Session Replay, Guides & Surveys,
     * LLM when the feature flag is on) that's been discovered for the
     * current integration. Routes through `enableFeature` so React
     * subscribers get notified and per-feature analytics fire.
     *
     * No opt-in picker — the unified browser SDK ships with all three
     * addons in one package. Quota / privacy concerns are surfaced via
     * per-option inline comments in the agent-generated init code, which
     * users can comment out individually — a clearer opt-out surface
     * than a one-shot picker.
     */
    autoEnableInlineAddons(source?: 'auto-tui' | 'auto-ci' | 'auto-agent'): void;
    /**
     * Set the additional feature currently being processed by the stop hook.
     * Used by the Run screen to render it as an in-progress task.
     */
    setCurrentFeature(feature: AdditionalFeature | null): void;
    /**
     * Mark a feature as completed by the stop hook (clears `current`).
     * Used by the Run screen to render it as a done task.
     */
    markFeatureComplete(feature: AdditionalFeature): void;
    /**
     * Enable an additional feature: enqueue it for the stop hook
     * and set any feature-specific session flags.
     * Respects Amplitude Experiment feature flags — if the corresponding
     * flag is off the feature is silently skipped.
     */
    enableFeature(feature: AdditionalFeature, source?: 'picklist' | 'auto-tui' | 'auto-ci' | 'auto-agent'): void;
    setAmplitudePreDetected(): void;
    /**
     * Blocks bin.ts until McpScreen resolves via resolvePreDetectedChoice().
     */
    waitForPreDetectedChoice(): Promise<boolean>;
    /**
     * Called from McpScreen when the user chooses to skip the agent (continue to
     * MCP) or run the full setup wizard anyway.
     */
    resolvePreDetectedChoice(runWizardAnyway: boolean): void;
    /**
     * Undo the pre-detection fast-path so runWizard can run; used when the user
     * opts into the setup agent after Amplitude was already found in the project.
     */
    resetForAgentAfterPreDetected(): void;
    setMcpComplete(outcome?: McpOutcome, installedClients?: string[]): void;
    setSlackComplete(outcome?: SlackOutcome): void;
    setOutroData(data: OutroData): void;
    /**
     * One-shot signal that the OutroScreen has been dismissed by the user
     * (keypress on cancel/error, picker action on success). Used by
     * `wizardAbort` to wait for the Outro to render and be acknowledged
     * before calling `process.exit` — without this, the process exits
     * while Ink is still flushing the previous frame and the user never
     * sees the OutroScreen at all on error paths.
     *
     * Resolves at most once. Subsequent calls to `signalOutroDismissed`
     * are no-ops; subsequent awaiters get a fresh pending promise that
     * will resolve on the next dismissal (in practice there's only ever
     * one dismissal per process).
     */
    private _resolveOutroDismissed;
    private _outroDismissedPromise;
    /** Returns a promise that resolves when the OutroScreen is dismissed. */
    outroDismissed(): Promise<void>;
    /** Mark the outro as dismissed. Idempotent. */
    signalOutroDismissed(): void;
    setFrameworkContext(key: string, value: unknown, autoDetected?: boolean): void;
    /**
     * Pop the most recently-answered framework setup question from
     * `frameworkContext`. Used by SetupScreen and by back-navigation past
     * FeatureOptIn. Returns `true` if an answer was popped.
     */
    popLastFrameworkContextAnswer(): boolean;
    /**
     * Clear post-Run state so a back-nav into pre-Run territory doesn't
     * leave the router short-circuited past the agent run / outro.
     *
     * Called by every reset helper that lands the user on a screen *before*
     * the Run entry. Without this, after a back-nav the router would see
     * `runPhase === Completed` and skip Run (and any post-run flow entries
     * with completed isComplete predicates), routing the user straight to
     * stale post-run state.
     *
     * `outroData` is also cleared because the OutroKind.Cancel branch in
     * router.resolve() jumps directly to Outro regardless of pipeline order.
     *
     * No-op when there's nothing to clear, so calling it is safe whether or
     * not the run actually started.
     */
    private clearPostRunStateForBackNav;
    /**
     * Revert past the Auth step back to RegionSelect. Region affects the
     * OAuth host, so we drop pending tokens, the cached org list, and any
     * resolved credentials so the next pass actually re-authenticates.
     */
    resetAuthForRegionChange(): void;
    /**
     * Confirm the silently-resolved org/project on a returning run.
     * Clears `requiresAccountConfirmation` so the Auth flow gate advances.
     */
    confirmAccount(): void;
    /**
     * Reject the silently-resolved org/project on a returning run and drop
     * back to the SUSI picker. Drops the cached selection AND credentials
     * so AuthScreen re-runs the OAuth-to-picker pipeline; the cached idToken
     * is still used (no fresh browser round-trip required) but the user can
     * pick a different org / project / environment this time.
     */
    rejectStoredAccount(): void;
    /**
     * Wipe in-memory auth state after the user confirmed /logout (disk
     * credentials are cleared by LogoutScreen itself). Routes through
     * `setKey` so per-key listeners (`listenKeys`) fire — direct
     * `store.session.X = null` assignments only notify version-bumped
     * subscribers and silently miss key-scoped subscriptions.
     */
    clearAuthForLogout(): void;
    /**
     * Revert past the Auth step back into the org/workspace picker. Keeps
     * credentials so we don't force a fresh OAuth round-trip — only the
     * picked identity is cleared.
     */
    clearOrgAndProjectSelection(): void;
    /** Re-run the activation check on the next visit to DataSetup. */
    resetActivationCheck(): void;
    /** Re-show the activation-options picker. */
    resetActivationOptions(): void;
    /** Re-show the feature opt-in picklist. */
    resetFeatureOptIn(): void;
    /** Re-show the MCP install picker. */
    resetMcp(): void;
    /** Re-enter the wait-for-events screen. */
    resetDataIngestion(): void;
    /** Re-show the Slack setup prompt. */
    resetSlack(): void;
    /**
     * The screen that should be rendered right now.
     * Derived from session state via the router.
     */
    get currentScreen(): ScreenName;
    /** Direction hint for screen transitions. */
    get lastNavDirection(): 'push' | 'pop' | null;
    /** Whether the user can step back from the current screen. */
    canGoBack(): boolean;
    /**
     * Step back to the previous decision. Returns true if a revert fired.
     * No-ops (returns false) when the active screen is a back-stop, has no
     * prior revertible step, or an overlay is currently active.
     */
    goBack(): boolean;
    /** True while a revert callback is executing inside goBack(). */
    private _reverting;
    getVersion(): number;
    /**
     * Notify React that state has changed.
     * The router re-resolves the active screen on next render.
     */
    emitChange(): void;
    pushOverlay(overlay: Overlay): void;
    popOverlay(): void;
    /**
     * Register a callback to run when transitioning onto the given screen.
     * Fires after every transition that lands on this screen.
     */
    onEnterScreen(screen: ScreenName, fn: () => void): void;
    /**
     * Detect screen transitions, run enter-screen hooks, and fire analytics.
     * Called at the end of emitChange/pushOverlay/popOverlay.
     */
    private _detectTransition;
    pushStatus(message: string): void;
    setTasks(tasks: TaskItem[]): void;
    updateTask(index: number, done: boolean): void;
    setEventPlan(events: PlannedEvent[]): void;
    /**
     * Cap on how many file-write rows we retain before evicting the oldest.
     * The inner agent occasionally touches dozens of files (skill installs,
     * lint passes, multi-step refactors) — keeping all of them alive in
     * the TUI would blow our render budget on small terminals. Recent
     * writes are the user-relevant signal; keep the tail.
     */
    static readonly MAX_FILE_WRITES = 50;
    /**
     * Append (or restart) a file write row from a `recordFileChangePlanned`
     * call. If the same absolute path is already in `'planned'` state, we
     * collapse the duplicate (MultiEdit fans out per file but the agent's
     * PreToolUse fires once per tool invocation — defensive).
     */
    recordFileChangePlanned(data: {
        path: string;
        operation: 'create' | 'modify' | 'delete';
    }): void;
    /**
     * Mark the most recent matching planned row as applied. We match the
     * most recent occurrence (in case the same file is rewritten later in
     * the run). If no planned row exists (the planned event was lost or the
     * applied event arrived first), append a synthesized row that's
     * already-completed — better to surface the write than silently drop
     * it.
     */
    recordFileChangeApplied(data: {
        path: string;
        operation: 'create' | 'modify' | 'delete';
        bytes?: number;
    }): void;
    /**
     * Apply a deterministic journey transition derived from a tool call
     * (see `src/lib/journey-state.ts`). Status of the named step is set to
     * the incoming value with a monotonic guard — once a step has reached
     * `completed`, no later transition demotes it. Sequential-ordering
     * cascade (a later step advancing implies all earlier ones are done)
     * is applied at render time.
     *
     * This is the source of truth for the canonical 5-step UI status.
     * `syncTodos` only refreshes per-step `activeForm` flavor text; it
     * never writes status here.
     */
    applyJourneyTransition(stepId: JourneyStepId, status: JourneyStatus): void;
    /**
     * Refresh per-step `activeForm` text from the agent's `TodoWrite`
     * output. Status is owned by `applyJourneyTransition`; this method
     * never touches it. Incoming todos that don't exactly match a
     * canonical label are dropped.
     *
     * Safe fallback: when no journey transition has fired yet (early in a
     * run, or in tests that exercise syncTodos directly), the renderer
     * still seeds the canonical 5 rows as `pending` so the user-visible
     * list is never empty.
     */
    syncTodos(todos: Array<{
        content: string;
        status: string;
        activeForm?: string;
    }>): void;
    /**
     * Re-render the canonical 5-row task list from the derived journey
     * status + per-step activeForms. Sequential-ordering cascade applied
     * here: when step N is in_progress or completed, every step before
     * N rolls up to completed (the agent has demonstrably moved past
     * them). Single-in-progress is implicit — the cascade walks earlier
     * in_progress states up to completed.
     */
    private renderJourneyTasks;
    subscribe(callback: () => void): () => void;
    getSnapshot(): number;
}
