/** Subset of Claude Code's JSONL event shape we care about. */
export interface TranscriptEvent {
    type?: string;
    uuid?: string;
    sessionId?: string;
    timestamp?: string;
    message?: {
        role?: string;
        content?: unknown;
    };
    /** Present on `type:"attachment"` lines. The bridge attribution queue
     *  treats `attachment.type === "queued_command"` as a turn-start signal —
     *  Claude writes one of these the moment it dequeues a type-ahead
     *  submission, immediately before the assistant's reply for that turn
     *  starts streaming. `prompt` carries the same content the user typed;
     *  shape is usually `string` but we tolerate the message-style array form
     *  via stringifyUserContent. */
    attachment?: {
        type?: string;
        prompt?: unknown;
        commandMode?: string;
    };
}
/** Extract the user-typed prompt text for a "turn start" event — works for
 *  both legacy `role:user` events (text in `message.content`) and the
 *  type-ahead `attachment(queued_command)` form (text in `attachment.prompt`).
 *  Returns '' when neither shape carries usable content. Used at three
 *  layers: BridgeTurnQueue.ingest (fingerprint-match the right pending Lark
 *  turn), worker emit (local-turn user-text resolution), and tests. */
export declare function extractTurnStartText(ev: TranscriptEvent | null | undefined): string;
export interface DrainResult {
    events: TranscriptEvent[];
    /** Byte offset to pass back on the next drain. */
    newOffset: number;
    /** Trailing partial line (no newline yet) — kept so the next drain can
     *  prepend it. Internal helper for chained drains; callers usually only
     *  need to remember `newOffset`. */
    pendingTail: string;
}
/**
 * Read everything from `path` starting at `fromOffset` and return parsed
 * JSONL events plus the new file offset.
 *
 * - Returns `{ events: [], newOffset: 0, pendingTail: '' }` if the file
 *   doesn't exist (caller treats this as "nothing yet").
 * - Detects truncation (size < fromOffset): resets to 0 and re-drains so a
 *   rotated/cleared transcript doesn't silently swallow new lines.
 * - Skips malformed JSON lines (logs nothing — robustness over noise).
 * - The trailing partial line (no `\n` yet) is *not* parsed and *not*
 *   counted toward `newOffset`, so the next drain re-reads it.
 */
export declare function drainTranscript(path: string, fromOffset: number): DrainResult;
/**
 * Filter to assistant text events. Returns only events where:
 *   - type === 'assistant' OR message.role === 'assistant'
 *   - content has at least one text block
 *   - uuid is present
 *
 * Sub-agent / sidechain events (isSidechain === true) are excluded so that
 * spawn-internal Task agent chatter doesn't leak to Lark.
 */
export declare function pickAssistantTextEvents(events: TranscriptEvent[]): TranscriptEvent[];
/**
 * Extract the visible text from one assistant event. Walks all `type:'text'`
 * blocks in `message.content` (or the bare string) and joins them with
 * blank lines. Returns '' if no text blocks.
 */
export declare function extractAssistantText(event: TranscriptEvent): string;
/** Convenience: filter+extract a list of events into a single concatenated string. */
export declare function joinAssistantText(events: TranscriptEvent[]): string;
/**
 * The turn's FINAL answer: the contiguous run of this turn's assistant-text
 * events after its last tool_use. A long agentic turn writes many interim
 * narration blocks between tool calls; joining them all (joinAssistantText)
 * makes the fallback both post a narration collage AND look "materially
 * longer" than the model's own explicit `botmux send`, defeating the
 * bridge-fallback gate. Walking back from the turn's last text event until a
 * tool_use / tool_result boundary yields just the closing answer.
 *
 * Crossed (not boundaries): thinking-only assistant lines and non-message
 * meta lines (`last-prompt`, `ai-title`, system, attachments) — Claude Code
 * interleaves these freely inside a closing answer. Events from other turns
 * never contribute: only uuids in `turnAssistantUuids` are collected.
 * Returns '' for a turn with no text after its last tool_use (nothing worth
 * falling back with — e.g. the turn ended in a `botmux send` call).
 */
export declare function trailingAssistantText(events: TranscriptEvent[], turnAssistantUuids: readonly string[]): string;
/** True when a `type:'user'` (or `message.role:'user'`) event represents a
 *  *real* prompt the human typed — not Claude Code's internal machinery
 *  (tool_result, slash-command wrappers, isMeta/isCompactSummary markers,
 *  sidechain spawn events). The bridge attribution queue and the adopt
 *  preamble extractor share this predicate to ensure they're seeing the
 *  same notion of "user input". */
export declare function isMeaningfulUserEvent(ev: TranscriptEvent | null | undefined): boolean;
/** True when a `type:'attachment'` line carries a queued-command payload
 *  representing a real submitted prompt. Claude writes one of these when it
 *  dequeues a type-ahead submission (right before the assistant's reply for
 *  that turn starts streaming) — the bridge attribution queue treats it
 *  exactly like a `role:user` event for turn-start purposes. Filters mirror
 *  isMeaningfulUserEvent's defenses (sidechain, empty / synthetic-prefix
 *  prompts) so a queued slash command can't false-start a Lark turn. */
export declare function isMeaningfulQueuedCommand(ev: TranscriptEvent | null | undefined): boolean;
export interface AdoptPreamble {
    /** The most recent meaningful user prompt's text (post-stringify, no
     *  whitespace collapse — preserves the prompt's actual formatting). */
    userText: string;
    /** All assistant visible-text emitted between that user prompt and the
     *  end of the events list, joined with blank lines. tool_use blocks are
     *  excluded; sidechain assistant events are excluded. */
    assistantText: string;
}
/** Walk the events forward and return the last *completed* user/assistant
 *  exchange. "Completed" here means: a meaningful user prompt followed by
 *  at least one assistant event with visible text. tool_use / tool_result
 *  events do NOT reset the turn — they're intra-turn machinery, so a
 *  prompt → tool_use → tool_result → assistant text sequence still counts
 *  as a single turn. Returns null when there's no meaningful user yet, or
 *  the last user wasn't followed by any visible assistant text (Claude is
 *  mid-tool-use when /adopt fired).
 *
 *  Used by adopt-bridge to surface "the previous round" to the Lark thread
 *  so the user has context for continuing the conversation. */
export declare function extractLastAssistantTurn(events: TranscriptEvent[]): AdoptPreamble | null;
/**
 * True when a user-role event carries ONLY tool_result blocks — Claude
 * Code's representation of "tool returned this output" between an
 * assistant tool_use and the assistant's continuation. Both the bridge
 * attribution queue and the on-disk fingerprint search must skip these:
 *
 *   - the queue would treat tool output as fresh local input and disable
 *     collection mid-turn,
 *   - the fingerprint search would false-positive on log content that
 *     happens to contain the Lark fingerprint substring (e.g. a short
 *     "hello" message hijacked by an unrelated jsonl whose tool_result
 *     dumped a log line containing "hello"). Re-exported by
 *     bridge-turn-queue.ts so both consumers share the same predicate
 *     and never drift apart.
 */
export declare function isPureToolResultUserEvent(content: unknown): boolean;
/**
 * Stringify a transcript user event's content to a flat string. Handles
 * both legacy bare-string content and the array-of-blocks form.
 *
 * Lives here (not in bridge-turn-queue.ts) so the in-process attribution
 * state machine and the on-disk fingerprint search use *exactly* the
 * same text — otherwise multi-line / array-content Lark messages stop
 * matching one path or the other and bridges silently break.
 */
export declare function stringifyUserContent(content: unknown): string;
/**
 * Collapse whitespace + trim. Same normalisation applied on both sides
 * of the fingerprint compare (the Lark message that produces the
 * fingerprint, and the transcript user content we search through),
 * so newlines / tabs / double-spaces don't break the match.
 */
export declare function normaliseForFingerprint(s: string): string;
/**
 * Find the most recently-modified `.jsonl` file in a Claude Code project
 * directory.
 *
 * `acceptCandidate` lets callers narrow the candidate set — the bridge's
 * quiet-mtime fallback passes a trust-set predicate so a sibling Claude
 * pane writing in the same project dir cannot hijack the watcher.
 * Without it any actively-written sibling jsonl wins the mtime race and
 * the bridge enters a flap loop with the pid resolver pulling it back.
 *
 * Returns null when the directory doesn't exist, has no jsonl files, or
 * every candidate was rejected by `acceptCandidate`.
 */
export declare function findLatestJsonl(dir: string, opts?: {
    acceptCandidate?: (path: string) => boolean;
}): string | null;
/**
 * Search every `.jsonl` file in `dir` for one whose contents include the
 * given fingerprint. Used by the bridge watcher to detect a session
 * switch (`/clear` / `/resume`) caused by the user's pane: when a Lark
 * message is pending and its content fingerprint shows up in a NEW jsonl
 * file, that file is the user's current session and we should switch.
 *
 * Pinning the switch decision to fingerprint match (rather than mtime)
 * avoids hijacking by sibling Claude Code panes in the same project
 * directory — they'll write busy jsonls but won't ever contain our Lark
 * fingerprint.
 *
 * Optional `excludePath` skips the file we're already watching so the
 * caller's "did it change?" comparison is cheap.
 *
 * Reads only the trailing 1 MB of each candidate (fingerprints land near
 * the end of the jsonl when Claude has just written them) — long-lived
 * sessions can grow to tens of MB so a full read would be wasteful.
 * Callers should still gate on "an unstarted pending turn exists" rather
 * than calling this on every poll tick.
 */
export interface JsonlFingerprintSearchOptions {
    /** Skip the file the caller is already watching/checking. */
    excludePath?: string;
    /** Ignore older files when the caller is looking for a just-written submit. */
    minMtimeMs?: number;
    /** Drop events whose `timestamp` field is older than this (millis since
     *  epoch). Defends against short fingerprints ("hello", "test") matching
     *  old user lines in unrelated sibling jsonls — file mtime alone isn't
     *  enough since a sibling Claude pane could be actively writing. */
    minEventTimestampMs?: number;
    /** Also match Claude Code type-ahead enqueue events, whose content is not role:user. */
    includeQueueOperations?: boolean;
    /** Called on each candidate that already passed the fingerprint match.
     *  Returning `false` skips the candidate and continues searching older
     *  files in the directory (mtime-descending walk). Used by the bridge
     *  watcher to reject sibling-pane jsonls whose sessionId we don't trust,
     *  without losing the chance to find a legitimate /clear rotation buried
     *  under a busier sibling. Default (no callback): accept the first
     *  fingerprint match like the original behaviour. */
    acceptCandidate?: (path: string) => boolean;
}
/** Scan a single jsonl file's tail for a Lark message fingerprint. Same
 *  parsing rules as `findJsonlContainingFingerprint` (decode role:user content,
 *  optionally also queue-operation/enqueue, normalise whitespace, then
 *  substring-match the fingerprint). Used by the claude-code adapter when
 *  the pid resolver has just switched to a rotated jsonl that may already
 *  contain the just-submitted user event. */
export declare function jsonlContainsFingerprint(path: string, fingerprint: string, opts?: {
    includeQueueOperations?: boolean;
    minEventTimestampMs?: number;
}): boolean;
export declare function findJsonlContainingFingerprint(dir: string, fingerprint: string, excludePathOrOptions?: string | JsonlFingerprintSearchOptions): string | null;
/**
 * Stronger sibling-pane recovery anchor than the substring fingerprint
 * search. Walks every `.jsonl` in `dir` and returns the paths whose
 * trailing 1MB contains a user/queue event whose normalised text is
 * EXACTLY equal to `normalisedContent` (not a substring), respecting
 * `excludePath`, `minMtimeMs`, `minEventTimestampMs`,
 * `includeQueueOperations`, and `acceptCandidate` the same way as
 * `findJsonlContainingFingerprint`.
 *
 * Returns *all* matches in mtime-descending order — callers must
 * abstain when the result has length > 1, since multiple files containing
 * the same exact normalised content cannot be disambiguated without
 * stronger evidence (and forcing a switch would risk picking the wrong
 * pane). The caller's typical pattern is:
 *
 *   - 1 match → switch to it (legitimate post-/clear recovery)
 *   - 0 matches → no recovery this tick; wait for stronger signal
 *   - >1 match → log and abstain; surface a diagnostic to the user
 *
 * Used by the bridge fingerprint fallback's recovery path for in-pane
 * `/clear`: substring matches risk hijacking on short fingerprints (the
 * literal text "test" matches "run tests" / "test bridge"), but full
 * equality on a Lark message we just wrote is a much stronger anchor.
 */
export declare function findJsonlsContainingExactContent(dir: string, normalisedContent: string, options?: JsonlFingerprintSearchOptions): string[];
/**
 * Partition transcript events into history (timestamp ≤ cutoff) and live
 * (timestamp > cutoff, or no parseable timestamp). Used by the bridge
 * watcher when it switches to a new jsonl that may contain pre-existing
 * conversation: anything older than the cutoff (e.g. iTerm-typed turns
 * the user produced before the Lark mark fired) belongs in the seen-set
 * via `BridgeTurnQueue.absorb` so the worker doesn't replay them as
 * "🖥️ 终端本地对话" cards. Anything newer is fed through `ingest()` so
 * the freshly-written Lark user event can match its pending fingerprint.
 *
 * Events with malformed / missing timestamps fall into `live`: better
 * to forward an unattributable event once than to silently drop a real
 * reply because Claude omitted a timestamp.
 */
export declare function splitTranscriptEventsByCutoff(events: TranscriptEvent[], cutoffMs: number): {
    history: TranscriptEvent[];
    live: TranscriptEvent[];
};
/**
 * Read the first event timestamp out of a jsonl. Reads only the leading
 * 4 KB — Claude's `file-history-snapshot` and `SessionStart` events both
 * land in the first few hundred bytes. Returns the parsed millis, or
 * undefined when no parseable timestamp is found in the leading chunk
 * (corrupted file, partial first line, format change).
 *
 * NOTE: not currently wired into the bridge rotation flow. The bridge
 * fingerprint fallback (`decideFingerprintSwitch` in
 * `bridge-rotation-policy.ts`) deliberately rejects candidates outside
 * the pid-derived trust set rather than relying on freshness heuristics
 * — file-creation timestamps cannot prove ownership across panes in
 * the same project dir. Kept here as a reusable primitive for
 * diagnostics and future /clear-recovery work.
 */
export declare function readFirstEventTimestamp(path: string): number | undefined;
//# sourceMappingURL=claude-transcript.d.ts.map