import type { AgentMessage } from "../internal/harness-types.js";
import type { ToolResultMessage } from "../internal/llm.js";
import type { Session } from "./session.js";
import type { ToolEffect } from "./types.js";
/**
 * Wake/resume side-effect reconciliation.
 *
 * Problem (design/07 · C3): a task can be interrupted (crash, kill, timeout, lost instance) in the
 * window between an assistant message with tool calls being persisted and that turn's tool results
 * being flushed. The session event log then ends with one or more **orphan tool calls** — a tool
 * call with no matching `toolResult`. On wake this is two bugs at once:
 *   1. Most chat APIs reject a request whose assistant tool_call is not followed by a tool result.
 *   2. The tool's `execute` may already have run (the side effect happened) — or may not have. The
 *      outcome is genuinely unknown.
 *
 * Contract enforced here:
 *   - **Never auto-replay a tool.** Reconstruction (`buildSessionContext`) already does not re-run
 *     tools; this guarantees we also never synthesize a "success" that hides an unknown outcome.
 *   - For each orphan an append can close (an id's last call site), append a synthetic `toolResult`
 *     (isError) that tells the model the call was interrupted and its outcome is unknown — making the
 *     transcript API-valid AND handing the decision to the model under its verification discipline.
 *   - Use the tool's {@link ToolEffect} to tune the message: `read` carries no risk at all; `idempotent`
 *     is replayable (re-issuing converges the state, but the interrupted attempt may have partially
 *     landed — RB-221); `write` (and unknown tools, conservatively) must be verified/confirmed
 *     before repeating.
 *
 * This runs on `acquire` of a resumed session, before the next turn starts. New/empty sessions and
 * cleanly-finished sessions have no orphans and are left untouched.
 */
/** One broken half of a tool call/result pair in the active branch (see {@link OrphanToolCall.kind}). */
export interface OrphanToolCall {
    toolCallId: string;
    toolName: string;
    /**
     * RB-398-a part 2: which half of the pair is missing.
     * - absent (the historical shape) = a **call-orphan**: an assistant `toolCall` with no matching
     *   `toolResult` — the reconcile-actionable kind ({@link reconcileInterruptedSession} closes it
     *   with a synthetic interrupted result).
     * - `"result"` = a **result-orphan**: a `toolResult` with no matching assistant `toolCall` — a
     *   protocol-invalid context every mainstream chat API rejects. The oracle used to walk only
     *   assistant→result, so it reported a context in exactly this state as clean while the request
     *   builder dropped the result (the request view's tool-turn law leaves it out and announces it,
     *   `orphan_result`). NOT reconcile-actionable (appending another result would not repair it); consumers
     *   deciding on repair must filter on this field.
     * Additive: absent = call-orphan, so pre-existing consumers and persisted expectations are
     * byte-identical for the historical direction.
     */
    kind?: "result";
}
/** The machine-readable discriminator on a reconcile-appended result — `details.errorKind` on the persisted
 *  message and `errorCode` on the synthetic `tool_end` frame, so a consumer never parses the text. Two-valued
 *  (a third value needs a cross-repo slip, see {@link reconcileInterruptedSession}). */
export type ReconciledErrorKind = "interrupted_never_started" | "interrupted_outcome_unknown";
/** One orphan closed by {@link reconcileInterruptedSession}, with everything the caller needs to mint the
 *  matching stream frame WITHOUT re-reading the session. */
export type RecoveredOrphan = OrphanToolCall & {
    /** The persisted toolResult entry id (message-identity Phase 1). */
    entryId: string;
    /** The model-facing text that was persisted — the synthetic `tool_end`'s `output` body. Handed back
     *  because the caller's frame is otherwise BODY-LESS: a consumer rendering tool output from frames saw an
     *  empty result for every interrupted call, while the model-facing transcript carried the full
     *  `[INTERRUPTED]` explanation (the two faces disagreed about the same call). */
    text: string;
    /** Which of the two honest states this orphan was closed in — the frame's `errorCode`. */
    errorKind: ReconciledErrorKind;
};
/** Outcome of reconciling a resumed session. */
export interface ReconcileReport {
    /** Orphan calls that were closed with a synthetic interrupted result (empty when nothing to do). `entryId` =
     *  the persisted toolResult entry id — message-identity Phase 1: the caller emits a `message_committed` for each
     *  so a consumer's `entryId → message` map stays complete for these reconcile-appended results (they render a
     *  `tool_end`). They are never a compaction floor (`toolResult` is excluded from cut points), so this is map
     *  completeness, not `preserved_segment` correctness. */
    recovered: RecoveredOrphan[];
}
/**
 * Find assistant tool calls in `messages` that have no matching `toolResult`.
 * Order follows first appearance in the transcript (orphans are normally only at the tail).
 *
 * `suspendedBatch` (design/45 §15.2 net-add #7): the set of tool-call ids belonging to a **deliberately
 * suspended** batch (the pending + not-yet-started calls of a durable-checkpoint suspension). These are
 * NOT orphans — they are waiting to be resumed via `runner.resume()`, not crash-interrupted. Wake-path
 * reconcile must skip them, or it would append `[INTERRUPTED]` results and **destroy the suspended
 * batch** (the bug this guard fixes: the wake/resume naming overload became real data destruction).
 * `runner.resume()` uses a checkpoint-aware entry that bypasses this reconcile entirely for those calls.
 */
export declare function findOrphanToolCalls(messages: AgentMessage[], suspendedBatch?: ReadonlySet<string>): OrphanToolCall[];
/**
 * backlog #389 伴生 (D-2) — CC 2.1.223's interruption markers, VERBATIM (`$U`/`CR` @ `CC:151120-151121`,
 * minted as a USER message by `Jce` @ `CC:640141-640154`). CC mints one on every abort whose reason is
 * outside `{"interrupt","refusal-fallback-edit"}` — and the interactive Esc / remote cancel, which is
 * what `TaskStream.interrupt()` corresponds to, is precisely on the minting side (`CC:1033698-1033733`).
 * The suppressed reason is the one case where the user's own replacement message is already the context.
 *
 * Taken verbatim rather than reworded: this string is an INPUT to later reasoning in CC (its own
 * "interrupted then immediately retried the same action" rule reads it back), and the constitution's
 * standing rule is that a question CC has answered is answered in CC's form.
 */
export declare const INTERRUPTED_BY_USER_MARKER = "[Request interrupted by user]";
/** backlog #389 伴生 — the tool-use variant (`CR`): the run was cut while a tool batch was in flight. */
export declare const INTERRUPTED_BY_USER_FOR_TOOL_USE_MARKER = "[Request interrupted by user for tool use]";
/**
 * backlog #389 伴生 (D-2) — append the interruption marker so the SESSION records that a person stopped
 * this run.
 *
 * Without it, an interrupt that lands on the model stream (no tool call in flight) leaves literally no
 * trace: the orphan reconcile has nothing to close, and the empty aborted assistant is deliberately not
 * persisted (`isEmptyFailureAssistant`). The next run on that session then reads a transcript in which
 * the half-finished work simply stops, and continues as if it had ended by itself.
 *
 * MUST be called AFTER {@link reconcileInterruptedSession} on the same interruption: a user message
 * placed between an assistant's tool calls and their results is exactly the invalid sequence the
 * reconcile exists to prevent.
 *
 * Returns the persisted entry id so the caller can mint its `message_committed` frame.
 */
export declare function appendInterruptionMarker(session: Session, opts: {
    toolUseInFlight: boolean;
}): Promise<string>;
/** One synthetic interrupted result: what a call with no recorded result is closed with. */
export interface InterruptedResult {
    toolCallId: string;
    toolName: string;
    /** The model-facing text. */
    text: string;
    /** Which of the two honest states the call is closed in. */
    errorKind: ReconciledErrorKind;
}
/**
 * The ONE mint of interrupted-result content for a batch of calls that have no recorded result. Two readers: the
 * persisted reconcile ({@link reconcileInterruptedSession}) and the request view's tool-turn law
 * (`engine/llm/tool-turns.ts`, which closes a call on the wire only and never writes the transcript) — so a call
 * closed either way reads the same to the model.
 *
 * `toolEffects` / `startedToolCallIds` as on {@link reconcileInterruptedSession}; absent ⇒ every call gets the
 * conservative unknown-outcome wording. The first call of each class in the batch states it in full and the rest
 * point back at it (scoped to this call, so a later batch always starts from the full wording again).
 */
export declare function interruptedResults(calls: readonly {
    toolCallId: string;
    toolName: string;
}[], toolEffects?: Map<string, ToolEffect>, startedToolCallIds?: ReadonlySet<string>): InterruptedResult[];
/** Is `message` an INTERRUPTED CLOSE — a result minted to stand in for one that was never recorded (this family's
 *  two-valued `details.errorKind`, written by the reconcile here and by the loop's turn-scoped interrupt)? A close
 *  says only "no result was recorded", so it is void beside a recorded result for the same call. Total: a tool's
 *  `details` is the tool's value, so an unreadable one reads as "not a close" (one guarded read per member). */
export declare function isInterruptedClose(message: {
    details?: unknown;
}): boolean;
/** The `toolResult` message for one {@link InterruptedResult} — the persisted and the request-view closes are this one shape. */
export declare function interruptedResultMessage(result: InterruptedResult, timestamp: number): ToolResultMessage<{
    errorKind: ReconciledErrorKind;
}>;
/**
 * Reconcile a resumed session's active branch: close any orphan tool calls with a synthetic
 * interrupted `toolResult` (never re-running the tool) — the ones an append can close
 * ({@link closableByAppend}), so a second reconcile of the same branch appends nothing. Returns what
 * was recovered.
 *
 * @param toolEffects optional name→effect map (from the task's tools). Unknown names are treated as
 *   `write` — the conservative default, so an unrecognized interrupted call is never said to be safe.
 * @param startedToolCallIds optional set of tool-call ids whose execution START the caller observed
 *   during the run being reconciled (the live-abort caller has this knowledge; the wake/crash path
 *   does not — the previous process is gone). When provided, an orphan absent from the set provably
 *   never started: it is closed with a "never executed — safe to re-issue" result instead of the
 *   conservative unknown-outcome wording. When omitted, every orphan keeps the conservative texts.
 */
export declare function reconcileInterruptedSession(session: Session, toolEffects?: Map<string, ToolEffect>, suspendedBatch?: ReadonlySet<string>, startedToolCallIds?: ReadonlySet<string>): Promise<ReconcileReport>;
