import { type GitRunner } from '../project.js';
import { type GhRunner, type LinkedPr, type BranchPrLookup } from './gh.js';
import type { Cached } from './cache.js';
import type { AutoHandoffSkip, AutoMergeOutcome, MergeWithheldReason } from '../events.js';
import { type AgentMeta } from '../store/index.js';
/** One commit a session put on its branch. */
export interface HandoffCommit {
    sha: string;
    /** Short sha, for display. */
    short: string;
    subject: string;
}
/** One file the session changed, against the branch point. */
export interface HandoffFile {
    path: string;
    insertions: number;
    deletions: number;
    /** True for a binary file, where line counts are meaningless. */
    binary: boolean;
}
/** What a finished session produced and what can still be done with it. */
export interface AgentHandoff {
    /** The branch the work is on. */
    branch: string;
    /** The branch still exists in the repo (a deleted or never-created one does not). */
    exists: boolean;
    /** What the branch is measured against (the repo's default branch), when one was found. */
    base?: string;
    commits: HandoffCommit[];
    files: HandoffFile[];
    insertions: number;
    deletions: number;
    /**
     * The session produced nothing to hand off: the branch exists but carries no commit the base
     * does not already have — or nothing beyond the framework's own bookkeeping (#1291), which is
     * committed for provenance, never as publishable work. Said out loud, rather than shown as an
     * empty branch.
     */
    empty: boolean;
    /** The repo has a remote to push to at all. */
    hasRemote: boolean;
    /** The branch is on the remote and the remote is at the same commit. */
    pushed: boolean;
    /** The branch is already merged into the base. */
    merged: boolean;
    /** The PR opened for this branch, when there is one. */
    pr?: LinkedPr;
    /** The PR is not known yet, rather than absent (#1028): the lookup is still running. */
    prPending?: boolean;
    /**
     * The files the session changed and never committed, read from its own checkout (#1173).
     *
     * The agent is instructed to commit what it *found*, never what it *wrote*, so a settled session
     * can hold its whole output in an uncommitted tree. That work is not on the branch yet, so it is
     * not in {@link commits} and it does not make {@link empty} false. Paths rather than a count,
     * because a no-diff branch must *name* what is waiting instead of offering an Open PR that
     * GitHub can only refuse. Absent when the caller did not say which checkout the session worked
     * in — "nobody asked" and "asked, tree clean" are different answers.
     */
    pendingFiles?: string[];
}
/** Injectable seams so the reader is unit-testable off disk, plus the checkout the session worked in. */
export interface AgentHandoffDeps {
    git?: GitRunner;
    pr?: BranchPrLookup;
    /**
     * The session's own checkout (#453), when it has one. The branch lives in the project repo and is
     * read from there; uncommitted work does not, it sits in the tree the agent actually edited.
     */
    checkout?: string;
    /**
     * When the agent started (ISO), so the PR lookup can tell the agent's own PR from an earlier agent's
     * on the same branch name (#1251). Without it, only an open PR is trusted.
     */
    since?: string;
}
/**
 * The branch an agent's work is on.
 *
 * Prefers what was recorded while the worktree existed (#799), because the built-in system prompt (#326) lets the
 * agent name its own branch, which makes both derivations below a guess. They stay as a fallback
 * for runs archived before the branch was recorded.
 */
export declare function agentBranchFor(agent: {
    id: string;
    branch?: string;
    sessionName?: string;
}): string;
/** The cached single-PR read {@link resolveAgentPr} asks for the recorded PR's current state. */
export type CachedBranchPrLookup = (cwd: string, branch?: string) => Promise<Cached<LinkedPr | undefined>>;
/** What {@link resolveAgentPr} needs to know about an agent: structurally satisfied by {@link AgentMeta}. */
export interface PrAgent {
    id: string;
    branch?: string;
    sessionName?: string;
    /** The pull request the agent recorded when one was opened for it (E6). */
    pr?: {
        number: number;
        url: string;
    };
}
/**
 * The pull request that belongs to an agent: the one it recorded (E6), read live for its state.
 *
 * The number is a fact about the agent, so the agent writes it down — at the moment its handoff opens
 * the PR, or when the dashboard's button does after the process is gone. Every surface then reads
 * the same integer instead of re-deriving it.
 *
 * What that replaced: a three-way branch-name ladder (the recorded branch, then the session-name
 * branch, then the run-id branch, because a hands-off web agent's checkout is gone and its session
 * name may be a reused pin) plus a timestamp heuristic on top of it, so that a predecessor's PR on
 * a shared branch name was not mistaken for this agent's. Three sources and a guess, standing in for
 * one integer nobody had written down — the same lesson the `branch` event (#1277) already learned.
 *
 * The *state* is still read live, because it changes without this agent doing anything: a PR merges,
 * a human closes it. That read rides the PR-lookup cache (#1028), and `pending` while it is warming means the
 * caller can ask again rather than render "no PR".
 */
export declare function resolveAgentPr(cwd: string, agent: PrAgent, prs?: CachedBranchPrLookup): Promise<Cached<LinkedPr | undefined>>;
/**
 * Merge a finished session's open PR (#1391): the Merge action, pressed by a human.
 *
 * The direct answer to the withheld-merge ending (#1363): a session whose agent never signalled
 * ready-for-merge leaves a draft PR behind, and this is the human saying "it's good, land it".
 * `ghMergePr` marks a draft ready on the way, for exactly that case. Refuses when the agent has no
 * PR or it is no longer open — "already merged" is an answer, not an action.
 */
export declare function mergeAgentPr(cwd: string, agent: PrAgent, deps?: {
    gh?: GhRunner;
    prs?: CachedBranchPrLookup;
}): Promise<HandoffResult>;
/**
 * Whether a branch is one a session made, rather than one the user did.
 *
 * Only a naming convention, so it is a guess for the case #326 allows — the agent picking its own
 * branch name. Every caller uses it to decide how loudly to surface something, never to act.
 */
export declare function isAgentBranch(branch: string | undefined): boolean;
/**
 * Read what a finished session left behind, from the project repo, for `branch`.
 *
 * Returns undefined only when `cwd` is not a git repo at all. A branch that no longer exists
 * still returns a handoff (with `exists: false`), because "that branch is gone" is itself the
 * answer the dashboard needs to show.
 */
export declare function readAgentHandoff(cwd: string, branch: string, deps?: AgentHandoffDeps): Promise<AgentHandoff | undefined>;
/**
 * Commit what a session left uncommitted, so what it did is what gets handed off (#1173).
 *
 * The automatic handoff commits the session's leftovers on the agent's way out, but that happens
 * when the agent process exits, and the finishing step is offered as soon as the agent settles
 * (#1178), which for a session left open for another turn is much earlier. Pressing the button is
 * the same instruction given by hand, so it sweeps the same leftovers into what it publishes. The
 * button only shows for a branch that already carries commits (#1173): a no-diff branch names its
 * uncommitted work instead of offering a step, so this never turns "nothing committed" into a PR
 * by itself.
 *
 * Two guards, because both failure modes end with the user's own work committed for them: the
 * checkout has to be the session's own (#453) rather than the project root that `resolveAgentCheckout`
 * falls back to once a worktree is gone, and it has to be sitting on the session's branch.
 *
 * Returns whether the handoff may go ahead: true when there was nothing to do, when the guards say
 * this is not ours to commit, or when the commit succeeded.
 */
export declare function commitAgentWork(checkout: string, projectCwd: string, branch: string, git?: GitRunner): Promise<boolean>;
/** The outcome of a handoff action, in the `{ ok }` shape the dashboard's `useAction` understands. */
/**
 * What a handoff action did. The PR's `number` rides along with its `url` (E6), because the number
 * is the fact worth *recording* — every surface that wants an agent's PR then reads it off the agent
 * rather than re-deriving it from branch names and timestamps.
 */
export type HandoffResult = {
    ok: true;
    url?: string;
    number?: number;
} | {
    ok: false;
    error: string;
};
/**
 * Push a finished session's branch to `origin`.
 *
 * Publishing the agent's work under the user's name is the user's call, but since #1102 that call
 * is made once, up front, by a checkbox that is armed by default, rather than re-taken by hand at
 * the end of every session. The click is still here for a session that opted out, and it is what
 * a failed auto-push falls back to.
 */
export declare function pushAgentBranch(cwd: string, branch: string, git?: GitRunner): Promise<HandoffResult>;
/**
 * {@link AgentHandoff.base} as a base a PR can actually be opened against.
 *
 * The field holds a git ref, because that is what every other use of it needs: `detectBase` reads
 * `refs/remotes/origin/HEAD`, so it is `origin/main`, and the log range and merged check are both
 * asking git a question about a remote-tracking ref. `gh pr create --base` is asking GitHub for a
 * *branch on the remote*, and rejects `origin/main` with "Base ref must be a branch".
 *
 * So the conversion belongs at the `gh` boundary rather than in the field. Stripping `origin/`
 * matches what the rest of this module already assumes: the remote is `origin` (`pushAgentBranch`
 * pushes there, `detectBase` reads its HEAD).
 */
export declare function prBaseName(base: string): string;
/**
 * The line of a failed git invocation worth showing.
 *
 * `execFile` rejects with "Command failed: git push ..." and buries git's own `fatal:` line
 * further down, which in a one-line panel means the user reads the command back instead of the
 * reason it failed.
 */
export declare function gitReason(err: unknown): string;
/** What to put on the PR. */
export interface PullRequestDraft {
    title: string;
    body: string;
    base?: string;
    /**
     * Open it as a GitHub draft (#1102). What auto-handoff uses: opening a PR by itself at the end
     * of every session should not put a review request in anyone's inbox.
     *
     * Safe to do only because the interventions queue was taught to keep listing a draft on a
     * session branch. Left off, a draft would be invisible in both places at once.
     */
    draft?: boolean;
}
/**
 * Open a PR for a finished session's branch, pushing it first when the remote does not have it.
 *
 * The button opens it ready for review, because a PR a human asked for by name is asking for
 * review. {@link PullRequestDraft.draft} is the auto-handoff case, which is not.
 */
export declare function openBranchPullRequest(cwd: string, branch: string, draft: PullRequestDraft, deps?: {
    git?: GitRunner;
    gh?: GhRunner;
}): Promise<HandoffResult>;
/**
 * Open a draft PR for a branch that exists only on the remote (#1601): a cloud session's own
 * `claude/*` branch was pushed from a VM this machine never sees, so there is nothing to push
 * here — `gh pr create --head` against the remote branch is the whole action, and gh's default
 * base (the repo's default branch) is the right one. Draft for the same reason the auto-handoff
 * opens drafts: a PR the framework opens by itself must not put a review request in anyone's
 * inbox, and the interventions queue keeps listing a session's draft.
 */
export declare function openRemoteBranchPullRequest(cwd: string, agent: HandoffAgent, branch: string, deps?: {
    gh?: GhRunner;
}): Promise<HandoffResult>;
/**
 * Open a PR for a finished session, deciding from what the agent recorded which cases should not
 * open one. Reads the branch's handoff first: a branch that no longer exists, or a session that
 * changed nothing, is a clear error rather than an empty PR, and a branch that already has a PR
 * returns that one. Title is the session name (else the intent's first line, else the id); body
 * is the intent plus which session did it. This is the handoff decision the dashboard's
 * open-PR button offers; the RPC layer only resolves which run it is about.
 */
export declare function openAgentPullRequest(cwd: string, agent: AgentMeta, options?: {
    draft?: boolean;
}): Promise<HandoffResult>;
/**
 * What a session was left armed to do when it ends (#1102).
 *
 * Both start true. The point of the feature is that the common case costs nothing: a session that
 * is simply left alone puts its branch on the remote and opens a PR for it.
 */
export interface HandoffIntent {
    push: boolean;
    pr: boolean;
    /**
     * Merge the PR once it is opened (#1216). Absent = off, unlike the pair above: landing work on
     * the default branch is not something to arm by default. No action-bar checkbox mutates it —
     * it comes settled off the agent's config.
     */
    merge?: boolean;
}
/**
 * Whether an armed merge may actually run (#1363), and if not, why.
 *
 * The rule settled on #1390: config *arms* the merge, the agent *authorizes* it. Landing on the
 * default branch unattended takes (a) the agent having declared the session done via
 * setReadyForMerge() — the same signal the on-before-mergeable step requires — and (b) the
 * framework not already knowing of work pending in this session (its own TODO file; never the
 * global queue, which is decoupled from sessions). A withheld merge is not a failed handoff:
 * push and PR go ahead, the PR just opens as a draft for a human.
 *
 * (b) is a temporary safety belt: the agent's word should ultimately be enough. Deleting it means
 * deleting `agentTodoOpen` here and `agentTodoPending` in todo-loop.ts.
 */
export declare function withheldMerge(deps: {
    readyForMerge: boolean;
    agentTodoOpen: boolean;
}): MergeWithheldReason | undefined;
/**
 * What auto-handoff did, so the agent can say it as an event (#835).
 *
 * A dashboard-started agent is spawned with `stdio: 'ignore'`, so anything printed here reaches
 * nobody: the outcome has to travel as an event or it does not travel at all. Skips are reported
 * for the same reason a skipped on-before-mergeable is — silence reads as "it ran and did nothing".
 */
export type AutoHandoffOutcome = {
    outcome: 'skipped';
    reason: AutoHandoffSkip;
    merge?: AutoMergeOutcome;
} | {
    outcome: 'done';
    pushed: boolean;
    url?: string;
    number?: number;
    merge?: AutoMergeOutcome;
} | {
    outcome: 'failed';
    step: 'push' | 'pr';
    error: string;
};
/**
 * Do the end-of-session handoff a session was left armed for (#1102): push the branch, open a
 * draft PR for it, or both.
 *
 * Reads the branch first and refuses on everything that is not a clean hand-off — a branch that is
 * gone, a session that committed nothing, a repo with no remote, a branch whose PR already covers
 * everything on it. Those are the cases where doing it anyway would produce a confusing artefact
 * rather than help. A merged PR the session kept working past is NOT one of them (#1512): the
 * work after the merge gets a fresh PR, or it reaches nobody.
 *
 * The PR is a draft on purpose. Opening one by itself at the end of every session must not put a
 * review request in anyone's inbox, and the interventions queue keeps listing a session's draft
 * so the work still comes back to the human.
 */
export declare function agentAutoHandoff(cwd: string, agent: HandoffAgent, intent: HandoffIntent, deps?: AgentHandoffDeps & {
    gh?: GhRunner;
}): Promise<AutoHandoffOutcome>;
/**
 * The little a handoff needs to know about the agent it is for: which branch, and what to say on
 * the PR. Narrower than {@link AgentMeta} so the agent process can call this before its meta is
 * final, and so a caller cannot quietly start depending on the rest of the agent's state.
 */
export type HandoffAgent = Pick<AgentMeta, 'id' | 'branch' | 'sessionName' | 'intent'> & Partial<Pick<AgentMeta, 'startedAt'>> & {
    /**
     * The GitHub issue the agent's ticket tracks (`#42`), when it implements one (#1334). Carried
     * into the PR title as `(fix #42)` so the squash-merge commit — which inherits the title —
     * closes the issue; without it an auto-merged quick-win leaves its ticket open.
     */
    fixes?: string;
    /**
     * The agent's own name for the work (#1618), from an `open-pr` block's first line: the PR
     * title when it wrote one. Absent, the title falls back to the session's name.
     */
    prTitle?: string;
    /**
     * The agent's own description of the work (#1567), from an `open-pr` block: the PR body
     * when it wrote one. Absent, the body describes what was asked for instead — which is all
     * the framework knows on its own.
     */
    description?: string;
};
//# sourceMappingURL=agent-handoff.d.ts.map