import { type WorkflowEvent } from './events/schema.js';
import { type ActivityState, type NodeState, type RunState, type Snapshot } from './events/replay.js';
import type { OutputRef } from './events/payloads.js';
export declare const TERMINAL_RUN_STATUSES: Set<string>;
export declare function isValidRunId(runId: string): boolean;
export declare function isValidPathSegment(s: string): boolean;
/**
 * Path-traversal guard — returns true iff `child`, after `..`/`.` resolution,
 * still lives inside `parent`.  Exported so dashboard surfaces that build
 * paths from caller-supplied ids (e.g. attempt terminal-log raw endpoint)
 * can apply the same defense-in-depth check on top of `isValidRunId` /
 * `isValidPathSegment`.
 */
export declare function isPathInsideDir(parent: string, child: string): boolean;
/**
 * Resolve the on-disk `terminal.log` path for a given attempt sidecar.
 * Production callers MUST validate runId / activityId / attemptId with
 * `isValidRunId` + `isValidPathSegment` first, and re-check `isPathInsideDir`
 * after joining to defend against any future segment-regex relaxation.
 */
export declare function attemptTerminalLogPath(runsDir: string, runId: string, activityId: string, attemptId: string): string;
/**
 * Resolve the on-disk raw `pty.log` path for a given attempt sidecar.
 * Same validation contract as `attemptTerminalLogPath` — callers MUST
 * pre-validate ids and re-check `isPathInsideDir` after joining.
 */
export declare function attemptPtyLogPath(runsDir: string, runId: string, activityId: string, attemptId: string): string;
export type RunRow = {
    runId: string;
    workflowId: string;
    status: string;
    lastSeq: number;
    dEf: number;
    dAct: number;
    dWait: number;
    updatedAt: number;
    failedNodeId?: string;
    errorCode?: string;
    errorClass?: string;
    errorMessage?: string;
    chatId?: string;
    larkAppId?: string;
};
export type ListRunsOptions = {
    /** Include terminal runs.  Default false (matches `botmux workflow ls`). */
    all?: boolean;
    /** Explicit status filter.  Wins over `all` when provided. */
    statuses?: Set<string>;
    /** Read chat-binding.json per row (extra fs op per run). */
    includeBinding?: boolean;
};
/**
 * Project every run in `runsDir` to a row.  Most-recently-updated first.
 *
 * - ENOENT on `runsDir` → `[]` (nothing to list).
 * - Non-directory entries / unreadable / corrupt event logs → skipped.
 * - Filter precedence: explicit `statuses` (any) > `all` (terminal kept) >
 *   default (terminal hidden).
 */
export declare function listRuns(runsDir: string, opts?: ListRunsOptions): Promise<RunRow[]>;
export declare function projectRunRow(runId: string, events: WorkflowEvent[], snap: Snapshot): RunRow;
export type RunSnapshotDTO = {
    runId: string;
    run: RunState;
    lastSeq: number;
    nodes: NodeState[];
    activities: ActivityState[];
    /**
     * v0.2 loop blocks indexed by their nodeId.  Optional so v0.1 clients
     * that don't render iteration timelines stay forward-compatible — if
     * the field is absent, no loops are present; if it's an empty record,
     * the workflow used loop schema but no loop instance ran.  See
     * /tmp/wf-loop-v02.md §8 (dashboard) + §9 (progress card).
     */
    loops?: Record<string, LoopSnapshotDTO>;
    dangling: {
        activities: string[];
        effectAttempted: string[];
        waits: string[];
        cancels: string[];
    };
    outputs: Record<string, OutputRef>;
    attemptIO: Record<string, AttemptIODTO>;
    chatBinding?: {
        chatId: string;
        larkAppId: string;
    };
    updatedAt: number;
};
export type LoopIterationDTO = {
    iteration: number;
    status: 'running' | 'approved' | 'rejected' | 'failed' | 'cancelled';
    bodyActivityIds: string[];
    decisionActivityId?: string;
    waitResolvedEventId?: string;
    decisionBy?: string;
    decisionComment?: string;
    timedOut?: boolean;
};
export type LoopSnapshotDTO = {
    loopId: string;
    status: 'running' | 'succeeded' | 'failed' | 'cancelled';
    iteration: number;
    maxIterations: number;
    iterations: LoopIterationDTO[];
    output?: OutputRef;
    errorCode?: string;
    errorClass?: string;
};
export type BlobPreviewDTO = {
    outputHash?: string;
    outputBytes?: number;
    contentType?: string;
    truncated?: boolean;
    value?: unknown;
    text?: string;
    error?: string;
    /** Set by `scrubSnapshotForUnauthed`: text/value were stripped because
     *  the caller wasn't authenticated.  Metadata (bytes, truncated) stays
     *  so the dashboard can render a "log available after login" placeholder
     *  instead of pretending the blob doesn't exist. */
    redacted?: boolean;
};
export type AttemptIODTO = {
    input?: BlobPreviewDTO;
    resolvedInput?: BlobPreviewDTO;
    output?: BlobPreviewDTO;
    log?: BlobPreviewDTO;
    terminal?: AttemptTerminalDTO;
    /** Full humanGate prompt when the producer spilled it to a blob via
     *  `promptRef`.  Read on demand via the same 64 KiB preview ladder as
     *  output / input blobs; cards never use this. */
    waitPrompt?: BlobPreviewDTO;
};
export type AttemptTerminalDTO = {
    sessionId: string;
    cliSessionId?: string;
    webPort: number;
    status: 'live' | 'closed';
    larkAppId?: string;
    botName?: string;
    cliId?: string;
    workingDir?: string;
    logPath?: string;
    startedAt: number;
    updatedAt: number;
    closedAt?: number;
    error?: string;
    /** True when a raw PTY byte log (`pty.log`) exists alongside the sidecar.
     *  Drives the replay viewer's "terminal cinema" vs "diagnostic log"
     *  toggle.  Older attempts predate this file and project as `false`. */
    hasPtyLog?: boolean;
};
/**
 * Scrub fields that leak raw CLI process bytes from a snapshot DTO before
 * exposing it to an unauthenticated reader.  Companion of the
 * `…/terminal-log/raw` cookie-auth carve-out: that carve-out hid the full
 * pty/terminal stream download, but the same data still leaked via
 * `attemptIO[*].log.text` (last 64 KiB tail of `terminal.log`) on the
 * public `/snapshot` endpoint.
 *
 * What stays public: run/node/activity status, output blob previews
 * (workflow author's intended product), terminal sidecar metadata.
 * What gets scrubbed: `io.log.text/value` (the raw stdout/stderr tail
 * — may contain env-var dumps, API key error messages, secret-bearing
 * curl responses) and `io.terminal.logPath` (absolute on-disk path
 * leaks filesystem layout).
 *
 * Idempotent + pure: caller is the route handler that already knows
 * `authed === false`.  Returns a new DTO; input is not mutated.
 */
export declare function scrubSnapshotForUnauthed(snap: RunSnapshotDTO): RunSnapshotDTO;
/**
 * Build a JSON-serializable snapshot for a single run.  Returns null when
 * the run is missing / has no events / has a corrupt log.  Callers
 * (dashboard `/snapshot` endpoint) should map null → 404.
 *
 * Always returns the full DTO including sensitive log bytes.  Callers
 * serving unauth'd HTTP requests MUST apply `scrubSnapshotForUnauthed`
 * before responding — kept as a separate step so internal callers
 * (cancel-run, daemon-side hooks) keep the full view without
 * round-tripping through scrub.
 */
export declare function readRunSnapshot(runsDir: string, runId: string): Promise<RunSnapshotDTO | null>;
export type EventWindowOptions = {
    /** Initial fetch: last N events.  Ignored if before/afterSeq is set. */
    tail?: number;
    /** Cursor: events with seq < beforeSeq, returned in seq-asc order. */
    beforeSeq?: number;
    /** Cursor: events with seq > afterSeq, returned in seq-asc order. */
    afterSeq?: number;
    /** Page size for before/afterSeq.  Default 200, max 1000. */
    limit?: number;
};
export type EventWindow = {
    events: WorkflowEvent[];
    oldestSeq: number | null;
    newestSeq: number | null;
    totalCount: number;
    hasOlder: boolean;
    hasNewer: boolean;
};
/**
 * Slice a run's event log into a paginated window.
 *
 * Mode precedence: `afterSeq` > `beforeSeq` > `tail` (default).  This
 * matches the dashboard usage: detail page first loads `?tail=100`,
 * then polls `?afterSeq=<newest>` and back-scrolls `?beforeSeq=<oldest>`.
 *
 * Pagination bookkeeping (`hasOlder` / `hasNewer`) is computed from the
 * full event list and the returned slice's bounds.  Returns null if the
 * runId is invalid or the run is missing.
 */
export declare function readEventWindow(runsDir: string, runId: string, opts?: EventWindowOptions): Promise<EventWindow | null>;
/**
 * Extract `<seq>` from a WorkflowEvent `eventId` of the form
 * `<runId>-<seq>` (events doc v0.1.2 §3.1).  Returns 0 for malformed
 * ids; callers should treat that as "unknown" rather than position 0.
 */
export declare function eventSeqFromId(eventId: string): number;
export declare function extractEventContext(payload: unknown): {
    nodeId?: string;
    activityId?: string;
    errorCode?: string;
};
//# sourceMappingURL=ops-projection.d.ts.map