/**
 * Parallel — fan-out composition: N branches run concurrently, then merge.
 *
 * Pattern: Builder (GoF) + Adapter over footprintjs's `addSubFlowChart`
 *          (fork children).
 * Role:    core-flow/ layer. All branches receive the same input
 *          `{ message }`. Each returns a string. A merge step combines
 *          them: either a pure function OR an LLM.
 * Emits:   agentfootprint.composition.enter / exit +
 *          composition.fork_start / branch_complete / merge_end
 *          (via compositionRecorder).
 */
import { type FlowchartCheckpoint, type StructureRecorder } from 'footprintjs';
import type { RunOptions } from '../core/runner.js';
import type { GroupMetadata, GroupTranslator } from '../core/translator.js';
import type { RunnerPauseOutcome } from '../core/pause.js';
import type { LLMProvider } from '../provider-contract/types.js';
import type { Runner } from '../core/runner.js';
import { RunnerBase } from '../core/RunnerBase.js';
import type { MemoryIdentity } from '../memory/identity/types.js';
export interface ParallelOptions {
    readonly name?: string;
    readonly id?: string;
    /**
     * Optional build-time recorders passed through to footprintjs's
     * `flowChart()` factory. Each recorder observes per-node build
     * events (`onStageAdded` / `onSubflowMounted` / etc.) for this
     * composition's internal chart (Seed + each branch mount + Merge).
     *
     * Cascade: each branch runner attaches its OWN recorders at its
     * own construction time. footprintjs does NOT propagate
     * StructureRecorders into mounted subflows — so for full coverage,
     * attach the same recorders to every nested composition. See the
     * core-flow README's "StructureRecorder cascade" section.
     *
     * When omitted, no build-time observation is wired up.
     */
    readonly structureRecorders?: readonly StructureRecorder[];
    /**
     * Optional per-COMPOSITION translator (UI-agnostic). When attached,
     * `runner.getUIGroup()` invokes it with the Parallel's
     * `GroupMetadata` (kind, id, name, branches list, merge strategy)
     * and returns whatever shape the translator produces.
     *
     * Independent of `structureRecorders` — those observe per-node spec
     * events, this shapes whole-composition UI groups. Common case is to
     * thread the SAME `GroupTranslator` reference through every nested
     * composition so `member.uiGroup` is populated recursively; L1c
     * per-method overrides add finer control.
     *
     * When omitted, `getUIGroup()` returns `undefined`.
     */
    readonly groupTranslator?: GroupTranslator;
}
export interface ParallelInput {
    readonly message: string;
    /**
     * WHO this run is for — the same tuple as `AgentInput.identity`. Every
     * member runs for it: its memory, its tools' `ctx.identity`. Omitted, every
     * member runs under this run's own conversation (`{ conversationId: runId }`),
     * never a namespace another run shares (`core/memberRun.ts`).
     */
    readonly identity?: MemoryIdentity;
}
export type ParallelOutput = string;
type BranchChild = Runner<{
    message: string;
}, string>;
export type MergeFn = (branchResults: Readonly<Record<string, string>>) => string;
/**
 * Outcome per branch in tolerant mode. One of:
 *   - `{ ok: true, value: string }` — branch succeeded; `value` is the returned string
 *   - `{ ok: false, error: string }` — branch threw; `error` is the error message
 *
 * Consumers in tolerant mode receive `Record<branchId, BranchOutcome>` and
 * decide how to handle partial failure (e.g., fall back to a default,
 * log, retry, or surface a user-facing message).
 */
export type BranchOutcome = {
    readonly ok: true;
    readonly value: string;
} | {
    readonly ok: false;
    readonly error: string;
};
export type MergeOutcomesFn = (outcomes: Readonly<Record<string, BranchOutcome>>) => string;
export interface MergeWithLLMOptions {
    readonly provider: LLMProvider;
    readonly model: string;
    /** Prompt prepended to the branch results when feeding the merge LLM. */
    readonly prompt: string;
    readonly temperature?: number;
    readonly maxTokens?: number;
}
interface BranchEntry {
    readonly id: string;
    readonly name: string;
    readonly runner: BranchChild;
    /**
     * Optional per-method translator override for THIS branch only.
     * When set, the branch's `member.uiGroup` is produced by invoking
     * this translator against the runner's own `GroupMetadata`, instead
     * of calling `branch.runner.getUIGroup()` (which would use the
     * runner's own constructor-level translator).
     */
    readonly groupTranslator?: GroupTranslator;
    /** This branch's failure rejects the whole run. See `ParallelBranchOptions.required`. */
    readonly required?: boolean;
}
/**
 * Options bag accepted by `ParallelBuilder.branch()` for per-method
 * overrides. Backwards-compatible with the legacy
 * `.branch(id, runner, name?)` signature: when the third arg is a
 * string it's still treated as `name`.
 */
export interface ParallelBranchOptions {
    /** Human-friendly name for this branch. Default: the branch id. */
    readonly name?: string;
    /** Per-method translator override. See `BranchEntry.groupTranslator`. */
    readonly groupTranslator?: GroupTranslator;
    /**
     * Mark this branch as REQUIRED: its failure rejects the whole Parallel
     * run — even under a tolerant `.mergeOutcomesWithFn()` merge — with an
     * error that names the branch. Default `false` (existing semantics:
     * strict merges aggregate failures at the join; tolerant merges receive
     * them as `BranchOutcome` entries).
     *
     * Fail-fast wiring: when EVERY branch is required, footprintjs's
     * fork-level `failFast` is engaged (`Promise.all`) so the first failure
     * aborts the fan-out immediately — siblings are not awaited and the
     * Merge stage never runs. When only SOME branches are required, the
     * fan-out stays best-effort (`Promise.allSettled`) and required
     * failures are enforced at the Merge join instead — footprintjs's
     * `failFast` is all-or-nothing per fork node, so engaging it for a
     * mixed set would wrongly abort the run when an OPTIONAL sibling
     * throws. See https://agentfootprint.dev/docs/getting-started/key-concepts/ (Parallel).
     *
     * Pause semantics under fail-fast: with every branch required, a branch
     * that PAUSES (`pauseHere()`) pre-empts its siblings the same way a
     * failure does — `Promise.all` settles on the first non-success, so
     * still-running siblings are not awaited before the run surfaces the
     * `RunnerPauseOutcome`. The checkpoint reflects the paused branch;
     * `resume()` continues from there and re-attributes any post-resume
     * required-branch failure just like `run()` does. Under the default
     * best-effort fork, a pause is only surfaced after every sibling
     * settles.
     *
     * Nested-mounting limitation: required-branch attribution and the
     * synthetic `composition.exit` are wired through `Parallel.run()` /
     * `Parallel.resume()`. When the Parallel's chart is instead MOUNTED
     * into an outer composition (e.g. `Sequence.step('s', parallel)`), the
     * outer runner's executor runs the chart — the fork-level `failFast`
     * still aborts the fan-out, but the rejection surfaces RAW (no
     * `required branch 'x' failed` wrapping) and the nested Parallel's
     * `composition.enter` is left without a matching `exit`. See README
     * Decision 8.
     */
    readonly required?: boolean;
}
/**
 * Per-branch first-error record captured during a run.
 *
 * `raw` is the ORIGINAL thrown value (footprintjs's
 * `FlowErrorEvent.structuredError.raw`) — the identity key used by
 * `rethrowWithBranchAttribution()` to correlate a fail-fast rejection back
 * to its branch regardless of the error's class name. `message` is the
 * BARE message (no `TypeError:` / `RateLimitError:` prefix) used in merge
 * aggregates, tolerant `BranchOutcome.error` strings, and the attributed
 * error text.
 *
 * @internal Exported only for `wrapBranchOutputMapper`'s signature — not
 * part of the public API (not re-exported from the package barrel).
 */
export interface BranchErrorRecord {
    readonly message: string;
    readonly raw: unknown;
}
type MergeStrategy = {
    readonly kind: 'fn';
    readonly fn: MergeFn;
} | {
    readonly kind: 'llm';
    readonly opts: MergeWithLLMOptions;
} | {
    readonly kind: 'outcomes-fn';
    readonly fn: MergeOutcomesFn;
};
export declare class Parallel extends RunnerBase<ParallelInput, ParallelOutput> {
    readonly name: string;
    readonly id: string;
    private readonly branches;
    private readonly merge;
    private readonly opts;
    private currentRunContext;
    /**
     * Per-branch first-error records captured during the current run.
     *
     * Filled by an internal CombinedRecorder attached in `createExecutor()`
     * that observes footprintjs `FlowErrorEvent`s. Errors are keyed by the
     * branch id (the first segment of `traversalContext.subflowPath`); only
     * the first error per branch is kept so the surface mirrors the wrapper
     * `try/catch` semantics that preceded the v0.x architectural refactor.
     *
     * Read by the Merge stage to populate strict-mode error messages and
     * tolerant-mode `BranchOutcome.error` strings, and by
     * `rethrowWithBranchAttribution()` to correlate fail-fast rejections by
     * error IDENTITY (`record.raw`). Cleared at the start of every `run()` /
     * `resume()`; writes are epoch-guarded (see `runEpoch`) so abandoned
     * fail-fast stragglers from a dead run cannot contaminate the live one.
     */
    private readonly branchErrors;
    /**
     * Monotonic run token — incremented at every `createExecutor()` (i.e.
     * each `run()` / `resume()`). The branch-error recorder captures the
     * epoch current at attach time and only writes while it is STILL
     * current. Under fail-fast, abandoned siblings of a rejected run keep
     * executing in the background; without this guard a late failure from
     * run N would land in run N+1's `branchErrors` (first-error-wins would
     * then block run N+1's real error).
     */
    private runEpoch;
    /** Ids of branches declared `{ required: true }`. See `ParallelBranchOptions.required`. */
    private readonly requiredIds;
    /**
     * True when EVERY branch is required — the fork node carries
     * footprintjs's `failFast` flag, so the first branch failure rejects
     * `executor.run()` with the RAW branch error before the Merge stage
     * can attribute it. `run()`/`resume()` re-attribute via
     * `rethrowWithBranchAttribution()`.
     */
    private readonly failFastEngaged;
    constructor(opts: ParallelOptions, branches: readonly BranchEntry[], merge: MergeStrategy);
    static create(opts?: ParallelOptions): ParallelBuilder;
    protected getGroupTranslator(): GroupTranslator | undefined;
    /**
     * Build the Parallel's `GroupMetadata` — kind `'Parallel'`, with one
     * `GroupMember` per branch. Each member exposes its `runner` plus
     * the runner's own `getUIGroup()` output (when the consumer
     * threaded the same translator through that branch's construction).
     */
    protected buildUIGroupMetadata(): GroupMetadata;
    run(input: ParallelInput | string, options?: RunOptions): Promise<ParallelOutput | RunnerPauseOutcome>;
    resume(checkpoint: FlowchartCheckpoint, input?: unknown, options?: RunOptions): Promise<ParallelOutput | RunnerPauseOutcome>;
    /**
     * Re-attribute a fail-fast abort to its originating branch.
     *
     * When `failFastEngaged` (every branch required), a failing branch
     * rejects `executor.run()` with the RAW branch error — the Merge stage,
     * which normally attributes failures by branch id, never runs. The
     * internal `parallel-branch-errors` recorder DID see the failure
     * (FlowRecorder.onError fires before the fork rejects), so correlate
     * the rejection against that map and wrap it in a branch-naming error.
     * Also emit the `composition.exit` (status `'err'`) the aborted Merge
     * stage could not, preserving enter/exit pairing for dashboards.
     *
     * Correlation is by error IDENTITY first (`record.raw === err` — the
     * recorder stores the ORIGINAL error object from
     * `FlowErrorEvent.structuredError.raw`), with bare-message equality as
     * a fallback for throws whose identity doesn't survive an engine
     * boundary (e.g. non-Error values). Identity matching is what makes
     * attribution work for ANY named Error subclass — `TypeError`, provider
     * SDK errors like `RateLimitError`, etc. — where message-only matching
     * would silently fail against name-prefixed strings.
     *
     * Rejections that don't correlate (engine errors, merge-stage errors,
     * non-fail-fast runs) are rethrown untouched.
     *
     * Only engaged on the `run()` / `resume()` path — a Parallel chart
     * MOUNTED into an outer composition rejects raw (see
     * `ParallelBranchOptions.required` JSDoc, "Nested-mounting limitation").
     */
    private rethrowWithBranchAttribution;
    /**
     * Find the branch whose recorded first error corresponds to `err`:
     * identity match on the original thrown value first, bare-message
     * equality second. Returns `undefined` when nothing correlates.
     */
    private correlateBranchError;
    /**
     * Synthetic `composition.exit` for fail-fast aborts (no stage scope to
     * emit from). Meta is built from the SAME run context the paired
     * `composition.enter` used (`buildEventMeta` + `currentRunContext`), so
     * the pair shares one real `runId` — Convention 4 run-scoping. Only the
     * `runtimeStageId` degrades (`'unknown#0'`): the abort happens outside
     * any stage, so there is no stage origin to attach.
     */
    private emitFailFastAbortExit;
    private createExecutor;
    /**
     * Build the internal recorder that captures first-error-per-branch.
     *
     * footprintjs's `SubflowExecutor` swallows subflow errors into
     * `parentContext.debug.addError(...)` and skips the `outputMapper`,
     * so the failed branch's error message never lands in parent scope.
     * To preserve the per-branch error surface the wrapper-based design
     * provided, we observe `FlowRecorder.onError` and correlate by the
     * first segment of `traversalContext.subflowPath`.
     *
     * Only the FIRST error per branch is kept. Errors fired outside any
     * branch (e.g., a Merge-stage error) are ignored.
     *
     * `epoch` is the run token current at attach time. Under fail-fast, a
     * rejected run's abandoned siblings keep executing in the background —
     * THIS recorder (attached to that dead run's executor) may still
     * receive their late `onError` events while a NEW run is live. The
     * epoch check drops those writes so the live run's map stays clean.
     */
    private makeBranchErrorRecorder;
    private finalizeResult;
    private buildChart;
}
/**
 * Wrap a branch's `outputMapper` so a mapper throw is ATTRIBUTED to its
 * branch before footprintjs swallows it.
 *
 * footprintjs's `SubflowExecutor` catches outputMapper errors into
 * `parentContext.addError('outputMapperError', ...)` WITHOUT firing
 * `FlowRecorder.onError` — so Parallel's internal `parallel-branch-errors`
 * recorder never sees them, and the Merge stage would fall back to
 * `'unknown error'` for the missing branch id. Recording into the
 * branch-error map here (first error per branch wins, mirroring the
 * recorder's semantics) closes that attribution gap. The error is then
 * RETHROWN so footprintjs's existing bookkeeping stays untouched: the
 * `outputMapperError` debug entry is still written and the branch id is
 * still absent from `branchResults` (which is how the Merge stage detects
 * the failure).
 *
 * @internal Exported for direct unit testing — not part of the public API
 * (not re-exported from the package barrel).
 */
export declare function wrapBranchOutputMapper(branchId: string, branchErrors: Map<string, BranchErrorRecord>, inner: (sfOutput: unknown) => Record<string, unknown>): (sfOutput: unknown) => Record<string, unknown>;
/** Fluent builder. Requires at least 2 branches + one merge strategy. */
export declare class ParallelBuilder {
    private readonly opts;
    private readonly branches;
    private merge;
    private readonly seenIds;
    constructor(opts: ParallelOptions);
    /**
     * Add a branch. All branches run concurrently with the same input.
     *
     * Third arg accepts EITHER a legacy bare `name` string (back-compat
     * with pre-L1c callers) OR a `ParallelBranchOptions` bag containing
     * `name` and/or a per-method `groupTranslator` override. The
     * override applies ONLY to this branch's `member.uiGroup` and does
     * not affect any other branch or the runner's own translator.
     */
    branch(id: string, runner: BranchChild, nameOrOpts?: string | ParallelBranchOptions): this;
    /**
     * Merge branch results via a pure function.
     * `fn` receives `{ [branchId]: string }` and returns the merged string.
     */
    mergeWithFn(fn: MergeFn): this;
    /** Merge branch results by feeding them to an LLM for synthesis. */
    mergeWithLLM(opts: MergeWithLLMOptions): this;
    /**
     * Tolerant merge — receives `{ [branchId]: BranchOutcome }` including
     * both successes (`{ ok: true, value }`) and failures (`{ ok: false, error }`).
     * Parallel does NOT throw on partial failure when this merge variant is
     * used; the consumer's `fn` decides how to handle it (fall back, surface
     * a warning, retry at a higher level, etc.).
     *
     * Use the default `mergeWithFn` / `mergeWithLLM` variants when you want
     * a single failing branch to abort the whole Parallel loudly.
     */
    mergeOutcomesWithFn(fn: MergeOutcomesFn): this;
    build(): Parallel;
}
export {};
