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; 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; /** 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; 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; dangling: { activities: string[]; effectAttempted: string[]; waits: string[]; cancels: string[]; }; outputs: Record; attemptIO: Record; 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; 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=` and back-scrolls `?beforeSeq=`. * * 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; /** * Extract `` from a WorkflowEvent `eventId` of the form * `-` (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