/**
 * Incremental sync of derived, redacted SessionTrajectory blobs to the
 * agents-traces R2 store.
 *
 * Security invariant: only the derived signal (steps + gaps + stats +
 * errorCount + programTimeShare) is uploaded — no raw transcript text.
 * redactSecrets() is called inside buildTrajectory() before we PUT.
 *
 * Sync gate: `sessions.db` `file_mtime_ms` is the source of truth.  A ledger
 * at `getRuntimeStateDir()/traces-sync.json` records the last sync timestamp
 * per device so re-runs skip unchanged sessions.
 *
 * Retry ledger: the mtime watermark alone cannot retry failures — a later-mtime
 * success advances it past an earlier failed row, which the next run's
 * `file_mtime_ms > watermark` filter then skips forever. So the same ledger also
 * records the identity + typed evidence of each failed session (`failures`), and
 * the row query unions those retry-worthy ids back in regardless of the watermark
 * (PHNX-3267). Failures are typed so a gone transcript (`transcript-unavailable`,
 * expected history, aged out after a TTL and never re-read) is distinguished from a
 * real parse/upload failure (retried until it resolves).
 */
import { type SessionTrajectory } from '../session/trajectory.js';
import { type BucketStats, type DriftSignal, type TraceFailureCause, type TraceTopicGroup } from './classify.js';
import { type FailurePattern } from './insights.js';
import type { LatencyInsight } from './segments.js';
import { buildSessionDetailV2 } from './schema2-build.js';
import type { SessionEvent } from '../session/types.js';
/**
 * The per-session shard body: the schema-2 rich `ToolExecution` detail. The
 * console decoder (prix/web) reads BOTH schema 1 and schema 2 and is the only
 * shard consumer, so emitting schema 2 is backward-compatible by construction —
 * no rollout flag is needed and none should exist (a producer that runs across
 * the fleet must not depend on an operator setting an env var). Both the upload
 * and dry-run paths call this one builder.
 */
export declare function buildSessionShard(traj: SessionTrajectory, events: SessionEvent[], knownSecrets: readonly string[] | undefined): ReturnType<typeof buildSessionDetailV2>;
export interface SyncOpts {
    /** Limit to N sessions (for testing / --dry-run); no limit when undefined. */
    limit?: number;
    /** When true, skip uploading the per-device index shard. */
    skipIndex?: boolean;
    /**
     * Compute the derived shards from this device's real sessions.db and WRITE
     * them to `outDir` instead of uploading. No Phoenix auth, no worker, no
     * network — a local export for verifying the real signal. Ignores the
     * incremental watermark so the export reflects every session.
     */
    dryRun?: boolean;
    /** Directory to write `index.json` + `sessions/<id>.json` when `dryRun` is set. */
    outDir?: string;
}
export interface SyncResult {
    uploaded: number;
    skipped: number;
    /** Total failures = transcriptUnavailable + parseFailed + uploadFailed. Kept for callers. */
    errors: number;
    /** The transcript file the row points at is gone/unreadable — expected history, not retried each run. */
    transcriptUnavailable: number;
    /** The transcript exists but could not be parsed into a trajectory — retried until it parses. */
    parseFailed: number;
    /** Parsed fine but the upload PUT failed (network/5xx) — retried on the next sync. */
    uploadFailed: number;
    /**
     * The index shard build or upload failed. Undefined on success. The per-session
     * data still uploaded (that loop runs first), but the aggregated console shard —
     * stats, needs-attention, failure clusters, latency — was NOT refreshed, so the
     * console keeps serving the last good index. Surfaced (not swallowed) so a stale
     * console is diagnosable instead of looking like a clean sync. See PHNX-3401.
     */
    indexError?: string;
}
/** Push derived, redacted trajectories for this device to the traces store. */
export declare function syncTraces(opts?: SyncOpts): Promise<SyncResult>;
export interface TracesIndexShard {
    schema: 1;
    device: string;
    syncedAt: number;
    owner: string;
    stats: {
        sessionsImported: number;
        /**
         * Median ACTIVE duration (span − idle gaps > 120s), ms — the meaningful figure
         * (PHNX-3457). Same key/shape as before this change, so the fleet-aggregate
         * worker (`worker-template.ts`) keeps weighted-averaging it unchanged; only its
         * VALUE moved from raw span to active time. The raw span stays available per
         * session on `SessionDetail.meta.spanMs`.
         */
        medianMs: number;
        /** p90 ACTIVE duration, ms. */
        p90Ms: number;
        /**
         * SEGMENTED active-time stats (PHNX-3472). The blended `medianMs`/`p90Ms`
         * above conflate one-shot interactive queries (63% of the corpus, ~15s
         * median) with substantial agent runs (~15min median), so they headline
         * neither. A session is an AGENT run when it made any tool call OR has more
         * than 8 messages; otherwise INTERACTIVE. These segment the same active-time
         * figure so the console can headline agent runs on their own axis. Each is
         * computed only over sessions with a non-null duration.
         */
        agentMedianMs: number;
        /** p90 ACTIVE duration over AGENT sessions, ms. */
        agentP90Ms: number;
        /** Median ACTIVE duration over INTERACTIVE sessions, ms. */
        interactiveMedianMs: number;
        /** (sessions with a non-null duration) / (total sessions), 0..1 — coverage of the duration stats. */
        measuredFraction: number;
        needAttention: number;
        toolErrorRate: number;
    };
    /**
     * Sessions excluded from the eval corpus as internal utility plumbing (PHNX-3474):
     * single-shot machine calls (no tool call AND ≤2 messages) or a known
     * internal-prompt signature (title generation, watchdog, commit-message, factory
     * worker). Every `stats` figure above, `topics` counts, and `needsAttention` are
     * computed over the AGENT set ONLY — `sessionsImported` is the real agent count,
     * not the raw row count. This is the number that was dropped.
     */
    utilityCount: number;
    needsAttention: IndexedSession[];
    topics: TopicItem[];
    failures: {
        byToolError: Array<{
            tool: string;
            desc: string;
            cause: TraceFailureCause;
            count: number;
        }>;
        byCause: Record<TraceFailureCause, number>;
    };
    /** Rolling 14-day window of per-bucket daily stats. Each inner array is one day. */
    bucketHistory: BucketStats[][];
    /** Movement signals for buckets with ≥3 days of history. */
    driftSignals: DriftSignal[];
    /** Cross-session failure clusters ranked by wasted time, from computeInsights(). Top-K, bounded. */
    failurePatterns: FailurePattern[];
    /** Sum of wastedMs across every cluster (not just the top-K rows above) — the headline number. */
    wastedMsTotal: number;
    /** Time-to-first-tool percentiles across this device's sessions. */
    latency: LatencyInsight;
    /**
     * Per-session roster — one flat scalar row per AGENT session (PHNX-3483), the
     * raw material the Rush console filters and re-aggregates live. Every `stats`
     * figure above is a pre-rolled scalar over the whole agent corpus; the console
     * cannot re-derive a filtered headline (e.g. "median for `claude` only") from a
     * scalar, so it needs the underlying rows. `durationMs` is the ACTIVE duration
     * (`sessionActiveMs`, the same value backing `stats.medianMs`; 0 when the span is
     * unmeasured), and `mode` encodes the AGENT-vs-INTERACTIVE segmentation
     * (`headless` = an agent run, `interactive` = a one-shot query) — the SAME
     * partition behind `stats.agentMedianMs` / `stats.interactiveMedianMs`, so a
     * mode-split median over the MEASURED rows reproduces them. The segmented stats
     * skip unmeasured (null-duration) sessions, which the roster still carries at
     * `durationMs: 0`, so a consumer reproducing the medians must exclude those the
     * same way (`measuredFraction` reports the covered share). Utility rows are
     * excluded, exactly like every other index statistic, so the roster length equals
     * the agent count (`stats.sessionsImported`). Optional here for schema
     * compatibility with a shard produced before this field.
     */
    sessions?: SessionRosterRow[];
}
/**
 * One per-session row in the index roster (PHNX-3483) — flat scalars only, so the
 * Rush console can filter the session set and re-aggregate the headline metrics
 * client-side without re-parsing transcripts. Built over AGENT rows only.
 */
export interface SessionRosterRow {
    id: string;
    /** `label ?? topic ?? classified-topic label`, secret-redacted. */
    title: string;
    /** The producing harness (`row.agent`). */
    harness: string;
    model: string;
    /** Short repo name from `project` / `cwd` basename / `git_branch`. */
    repo: string;
    /**
     * `headless` = an agent run (any tool call OR more than 8 messages),
     * `interactive` = a one-shot query — the SAME split behind
     * `stats.agentMedianMs` / `stats.interactiveMedianMs`.
     */
    mode: 'interactive' | 'headless';
    /** Corpus topic group of the session; `code` when unclassified. */
    projectType: TraceTopicGroup;
    /** Session start, epoch ms. */
    startedAt: number;
    /** ACTIVE duration in ms (`sessionActiveMs`); 0 when the span is unmeasured. */
    durationMs: number;
    toolCount: number;
    errorCount: number;
    needsAttention: boolean;
    /** Best-effort — omitted when the source figure is unavailable. */
    costUsd?: number;
}
export interface IndexedSession {
    id: string;
    title: string;
    repo: string;
    device: string;
    /** The harness that produced the session (claude/codex/rush/grok/…). Same as `harness`. */
    agent: string;
    model: string;
    /** Corpus classification (PHNX-3474). Always `'agent'` here — utility rows are excluded. */
    kind: SessionKind;
    severity: number;
    flags: string[];
}
/**
 * One example session under a topic tile — the shape the console drill-down consumes.
 * Carries `kind` + `harness` (PHNX-3474) so the console can filter a tile's session
 * list by corpus class and by harness. Refs on a topic tile are always `'agent'`
 * (utility rows never reach a bucket), but the field is explicit for the consumer.
 */
export interface TopicSessionRef {
    id: string;
    title: string;
    kind: SessionKind;
    harness: string;
}
/**
 * Corpus class of a session (PHNX-3474). `utility` is internal machine plumbing —
 * a single-shot call with no tool use and ≤2 messages, or one whose topic/label
 * matches a known internal-prompt signature (title generation, watchdog,
 * commit-message writer, factory worker). Everything else is `agent`: real agent
 * work the Evals console counts and scores. Utility rows are tagged, never deleted,
 * and excluded from every index statistic.
 */
export type SessionKind = 'utility' | 'agent';
/**
 * Classify a session as internal `utility` plumbing vs real `agent` work (PHNX-3474).
 * `toolCallCount` is the AUTHORITATIVE per-session tool-call count from the loaded
 * `tool_calls` rows (the row's own `tool_call_count` column is a fallback for a row
 * whose calls weren't loaded). A session is `utility` when a known internal-prompt
 * signature matches its topic/label, OR it made no tool call AND has ≤2 messages —
 * the single-shot machine-call shape. Otherwise it is `agent`.
 */
export declare function classifySessionKind(row: Pick<SyncRow, 'topic' | 'label' | 'message_count' | 'tool_call_count'>, toolCallCount: number): SessionKind;
/**
 * One topic bucket in the treemap. `sessions` carries up to {@link TOPIC_SESSION_CAP}
 * example refs so the console can drill from the tile into its session list — a tile
 * with no refs renders display-only (PHNX-3408). `count` stays the true total.
 */
export interface TopicItem {
    key: string;
    label: string;
    count: number;
    group: TraceTopicGroup;
    sessions: TopicSessionRef[];
}
/** A row from `tool_calls`. `ordinal`/`timestamp` order calls within a session for computeInsights(). */
export interface ToolCallRow {
    session_id: string;
    ordinal: number;
    timestamp: string;
    /**
     * When the call's result arrived — its own end time (PHNX-3437). Optional
     * because rows an older extractor stored, and calls that never produced a
     * result, carry NULL; `computeInsights` falls back to the bounded inter-call
     * gap when it is absent.
     */
    end_timestamp?: string | null;
    tool: string;
    outcome: string;
    exit_code: number | null;
    status_code: number | null;
    error_code: string | null;
    error: string | null;
    parse_error: string | null;
}
/**
 * Active time for a session in the index shard: its recorded span minus every idle
 * gap > 120s (PHNX-3457). The index build already holds the ordered `tool_calls`
 * rows for the whole corpus, so idle is derived from them here — no transcript
 * re-parse. A cursor sweeps the span from `sessionStartMs`: each stretch where the
 * cursor sits idle for more than the threshold before the next call starts is
 * subtracted, and idle is measured from a call's END (its own `end_timestamp` when
 * known, else its start) so a call's own blocking duration is never mistaken for
 * idle. Crucially the sweep also books the gaps at the two BOUNDARIES — before the
 * first call and after the last call to the session end — so a session with a lone
 * tool call that was then abandoned and resumed hours later (the case a
 * between-calls-only measure missed entirely, leaving the whole 345h span counted
 * as active) has that trailing idle stripped. A session end is `sessionStartMs +
 * spanMs`, so the two agree by construction.
 *
 * Bounded to `[0, spanMs]`. A session with NO tool calls returns the full span
 * unchanged rather than a fabricated zero: there is no tool-call evidence of idle
 * either way, and treating a chat-only turn as 100% idle would be a worse error
 * than leaving its span uncorrected. Where the full event stream IS available (a
 * per-session `SessionDetail`), {@link activeMsFromTrajectory} is used instead —
 * it sees message events this call-only approximation cannot, so the two are close
 * but not identical by design (the corpus-scale index build cannot afford the
 * per-session parse the detail view does).
 */
export declare function sessionActiveMs(spanMs: number, sessionCalls: ToolCallRow[], sessionStartMs: number): number;
/** Active time from an already-built trajectory: span minus its idle gaps (all > threshold). */
export declare function activeMsFromTrajectory(traj: SessionTrajectory): number;
/** Human description of a failed call, keyed by (tool, desc, cause) for grouping. Exported for computeInsights(). */
export declare function failureDescription(call: ToolCallRow, cause: TraceFailureCause): string;
/** Build the redacted rich console shard from indexed metadata and derived caches. */
export declare function buildIndexShard(rows: SyncRow[], device: string, owner: string, prevShard?: TracesIndexShard | null): TracesIndexShard;
/** Per-session drill-down detail the console consumes (sessions/<id>.json). */
export interface SessionDetail {
    schema: 1;
    id: string;
    meta: {
        /** Raw wall-clock span (last event − first event), idle time included. */
        spanMs: number;
        /**
         * Active time: `spanMs` minus every idle gap > 120s (PHNX-3457). A session
         * resumed hours later, or left idle mid-turn, inflates `spanMs` with wall-clock
         * the agent did no work in — active time strips those gaps so a duration reads
         * as effort, not calendar span. This is what the console's duration median/p90
         * should trust; `spanMs` stays available as the raw figure.
         */
        activeMs: number;
        turns: number;
        tools: number;
        errorCount: number;
        tokens: number;
        costUsd: number;
        outcome: string;
        repo: string;
        agent: string;
        model: string;
    };
    steps: SessionTrajectory['steps'];
    gaps: SessionTrajectory['gaps'];
    /** Steps dropped from `steps` when a run was too long — surfaced so truncation is never silent. */
    truncatedSteps: number;
    whereItWentWrong: string | null;
    /**
     * Failed tool steps, surfaced even when `meta.outcome === 'completed'` — a run
     * can finish without throwing while a tool call inside it failed (the
     * LangSmith trap: a green run-level status hiding a real failure).
     */
    surfacedToolFailures: Array<{
        tool?: string;
        label: string;
        detail?: string;
    }>;
}
/** Plain-language summary of the friction in a run, or null when it ran clean. */
export declare function buildWhereItWentWrong(traj: SessionTrajectory): string | null;
/**
 * Truthful run-level outcome (PHNX-3387).
 *
 * A run with zero tool errors `completed`. A run that hit tool errors is
 * `completed` ONLY when it *causally recovered* — a substantive, non-human-facing
 * tool step succeeded strictly after the last error AND resolved the failed work
 * (its work signature matches an errored step's), the exact predicate the
 * false-termination phenotype uses ({@link recoveredAfterErrors}). A run whose
 * last substantive step is the error, whose only post-error steps are human-facing
 * (a punt to `AskUserQuestion` — the case the broken "last tool call ok" heuristic
 * mislabeled `completed`), or whose only post-error success is unrelated work (a
 * failed `bun test` followed by an incidental `ls`) stays `errored`.
 *
 * This is what makes `surfacedToolFailures` on a `completed` run honest: those
 * are failures the run recovered from, not a green status hiding an unresolved
 * failure. It never flips a run that ended unresolved to `completed` (no
 * regression vs the old `errorCount > 0 ? errored : completed`), and it does not
 * flip a run whose failed work was never resolved just because some later,
 * unrelated call happened to succeed.
 */
export declare function deriveRunOutcome(traj: SessionTrajectory): 'completed' | 'errored';
/**
 * Map the derived trajectory to the console's SessionDetail shape, stripping
 * local-machine PII (full cwd, account) that would expose filesystem paths if
 * written to R2. `repo` is the cwd basename only.
 */
/**
 * The `meta` block shared by the schema-1 {@link SessionDetail} and the schema-2
 * `SessionDetailV2`. Strips local-machine PII (full cwd, account): `repo` is the
 * cwd basename only. Factored so both producers stamp identical meta.
 */
export declare function buildDetailMeta(traj: SessionTrajectory): SessionDetail['meta'];
export declare function buildSessionDetail(traj: SessionTrajectory): SessionDetail;
/** How a session failed to sync. Only parse/upload failures are re-queried every run. */
export type SyncFailureKind = 'transcript-unavailable' | 'parse-failed' | 'upload-failed';
export interface SyncFailure {
    id: string;
    mtimeMs: number;
    kind: SyncFailureKind;
    /** Bounded, redacted first line of the underlying error — actionable evidence. */
    detail: string;
    /** Epoch ms first recorded, so a permanently-unavailable transcript can be aged out. */
    firstSeen: number;
    attempts: number;
}
interface SyncLedger {
    lastSyncMtime?: number;
    /** Failed session identities, keyed by id, that survive across syncs. */
    failures?: SyncFailure[];
}
export declare function readSyncLedger(): SyncLedger;
export declare function writeSyncLedger(ledger: SyncLedger): void;
/**
 * True once the user has run `agents traces sync` at least once (the ledger
 * file exists). This is the opt-in signal the run-exit auto-sync gates on
 * (PHNX-3628): a user who has never synced has not opted into the traces store,
 * so an `agents run` never uploads on their behalf.
 */
export declare function hasSyncedBefore(): boolean;
export interface SyncRow {
    id: string;
    short_id: string;
    agent: string;
    origin: string | null;
    routine_name: string | null;
    routine_run_id: string | null;
    version: string | null;
    account: string | null;
    account_key: string | null;
    account_org: string | null;
    mode: string | null;
    timestamp: string;
    last_activity: string | null;
    project: string | null;
    cwd: string | null;
    git_branch: string | null;
    topic: string | null;
    label: string | null;
    message_count: number | null;
    token_count: number | null;
    output_tokens: number | null;
    input_tokens: number | null;
    cache_read_tokens: number | null;
    cache_write_tokens: number | null;
    cost_usd: number | null;
    cost_usd_nocache: number | null;
    duration_ms: number | null;
    model: string | null;
    tool_call_count: number | null;
    file_path: string;
    file_mtime_ms: number | null;
    file_size: number | null;
    machine: string | null;
}
export {};
