/** Terminal result delivered back to the parent's `delegate` tool call. */
export interface DelegateResultPayload {
    status: "completed" | "failed" | "timeout" | "terminated";
    output: string;
    error?: string;
}
/**
 * Snapshot of an in-flight delegate persisted to `active-delegates.json`.
 * The in-memory resolver is intentionally not persisted — it cannot survive a
 * restart by definition. After restart, `_loadFromDisk` repopulates the
 * pending map with metadata only; a `submit` arriving before the parent
 * re-registers is latched, draining on the next `register` call.
 */
export interface ActiveDelegate {
    parentSessionId: string;
    /**
     * Tool-use id of the parent's `delegate` call. For `parallel:` invocations
     * the harness key is `${toolUseId}#${i}` so each parallel slot is tracked
     * independently — callers must include the `#i` suffix here when
     * registering parallel delegates.
     */
    toolUseId: string;
    delegateSessionId: string;
    cwd: string;
    title?: string;
    sandboxed?: boolean;
    instructions: string;
    timeoutMs: number;
    createdAt: number;
}
/**
 * Minimal subset of `SessionManager` the harness depends on. Kept narrow so
 * unit tests can supply a stub or `undefined`.
 */
export interface DelegateHarnessSessionManager {
    addTerminationListener?(fn: (sessionId: string, info: {
        projectId?: string;
        reason: "terminated" | "archived" | "purged";
    }) => void): void;
}
export type DelegateBroadcastFn = (sessionId: string, event: unknown) => void;
export declare class DelegateHarness {
    private pending;
    private latched;
    /**
     * Keys that have already had a terminal result delivered (resolved a
     * pending awaiter). Subsequent `submit()` calls for the same key are
     * dropped — first-write-wins idempotency. Bounded over time by
     * `register()` clearing the entry when a key is recycled (i.e. a brand
     * new tool_use_id is registered for the same parent + session).
     */
    private completed;
    /**
     * Persisted-but-no-awaiter entries reconstructed from disk on restart.
     * The original `register()` closures (resolve/reject) lived in v8 heap and
     * are gone, so calling resolve() on them after restart would dead-end. We
     * keep them as metadata-only shells: `submit()` against a shell-only key
     * latches the result instead of resolving (so the parent's next
     * `register()` POST drains it). Cleared as soon as the parent
     * (re-)registers a real resolver for the same key.
     */
    private shells;
    private readonly persistPath;
    constructor(stateDir: string, sessionManager?: DelegateHarnessSessionManager, broadcastFn?: DelegateBroadcastFn);
    /**
     * Persist metadata about an in-flight delegate WITHOUT creating a parked
     * Promise. Used by the live path (`SessionManager.createDelegateSession`)
     * so the on-disk state exists before the parent's `/wait` POST arrives.
     *
     * If a fast child completes via `submit()` before the parent registers,
     * the harness sees no `pending` entry (only this shell), and `submit()`
     * latches the result. The parent's later `register()` call drains the
     * latch immediately. This avoids the live-path race where a
     * pre-registered Promise would consume the result fire-and-forget,
     * leaving the parent's real wait to hang.
     *
     * Idempotent: calling with an existing `(parent, toolUseId)` key is a
     * no-op when the harness already has a real `pending` entry (parent
     * already arrived). When the key is fresh, it adds a shell.
     */
    recordActive(active: ActiveDelegate): void;
    /**
     * Register a parked Promise for the given delegate. Returns immediately
     * with a resolved Promise if a result is already latched for the same key.
     *
     * If a pending entry already exists for the key (e.g. parent reconnected
     * after a transient socket drop), the previous Promise is rejected with
     * `"superseded"` and replaced.
     */
    register(active: ActiveDelegate): Promise<DelegateResultPayload>;
    /**
     * Cancel a single in-flight delegate. Used by:
     *   - the `/api/internal/delegate/wait` hard-timeout path, and
     *   - the `/api/internal/delegate/cancel` parent-abort endpoint.
     *
     * Resolves any pending awaiter with a structured `terminated` payload
     * (so the parent's `/wait` HTTP handler observes the same shape it would
     * for a normal terminal submit), then drops shell + latched state and
     * marks the key completed so a racing submit is a no-op. Returns `true`
     * if anything was cleaned up.
     *
     * This is the cleanup-friendly counterpart to `submit()`: submit against
     * a shell-only key would *latch* the terminated result and leave the
     * shell live, which is wrong for an abort because no parent will ever
     * acknowledge the latch — it would persist in `active-delegates.json`
     * indefinitely.
     */
    cancel(parentSessionId: string, toolUseId: string, reason?: string): boolean;
    /**
     * Submit a terminal result. Returns `true` if a pending Promise was
     * resolved; `false` if the result was latched (submit-before-register)
     * OR if it was a no-op (duplicate against an already-completed key, or
     * duplicate against an already-latched result — first-write-wins,
     * mirroring `verification_result`'s idempotency contract).
     *
     * Idempotency contract: once a (parentSessionId, toolUseId) has
     * received a terminal result — whether by resolving a pending awaiter,
     * latching for a future register, or being explicitly cancelled — every
     * subsequent submit for that key is a no-op for the lifetime of the
     * harness instance. This prevents duplicate `agent_end`/`process_exit`
     * events or a retried `/api/internal/delegate/submit` from poisoning
     * `active-delegates.json` with a second result.
     */
    submit(parentSessionId: string, toolUseId: string, result: DelegateResultPayload): boolean;
    /**
     * Confirm the parent has received the result for `(parentSessionId,
     * toolUseId)`. Removes the latched entry from disk and marks the key
     * as fully completed (any straggler `submit()` for the same key is a
     * no-op). Idempotent.
     *
     * Called from the `/api/internal/delegate/wait` handler after the
     * HTTP response has been written, and from `register()` on a recycled
     * key (parent's tool extension reconnects with a fresh delegate).
     */
    acknowledge(parentSessionId: string, toolUseId: string): boolean;
    /**
     * Reject every pending wait whose parent matches and drop matching latched
     * entries. Returns the list of delegate session ids the caller should
     * cascade-terminate.
     */
    rejectAllForSession(parentSessionId: string, reason?: string): string[];
    /**
     * Set of delegate session ids that have an in-flight (pending) wait. Used
     * by `SessionManager.restoreSessions()` to decide which delegates to
     * eager-restore after a server restart.
     */
    getActiveDelegateSessionIds(): Set<string>;
    /** Snapshot of active delegates (live pending + post-restart shells). */
    getActiveDelegates(): ActiveDelegate[];
    /**
     * Re-load pending+latched state from disk. Public so the server-side
     * resume path (`resumeInterruptedDelegates`, owned by a later task) can
     * reconstruct the harness's view of the world after restart and decide
     * which children to eager-restore vs. drain a synthetic failure for.
     *
     * For this task we only need the no-op return shape; later wiring will
     * iterate the returned active list.
     */
    resumeInterruptedDelegates(): ActiveDelegate[];
    /** Atomic write-then-rename. Recreates parent dir on demand. */
    private _persist;
    /**
     * Tolerant load. Absent or malformed file → empty state. Pending entries
     * are reconstructed with placeholder rejecting resolvers — the closures
     * the original `register()` call held are gone, so the resurrected
     * Promises would never resolve. Instead we treat persisted-pending as
     * "shells" that the next `register()` from the parent supersedes (which
     * also drains any latched result that arrived in the meantime).
     */
    private _loadFromDisk;
    /** Test-only: path of the persistence file. */
    get _persistPathForTest(): string;
}
/**
 * Resolve the effective `kind` for a persisted session. Pre-`kind` records
 * (created before this discriminator existed) are inferred from `delegateOf`
 * — anything with a parent is a delegate, everything else defaults to
 * `"worker"`. Reviewer sessions set `kind` explicitly at creation time.
 *
 * Importable from anywhere — keeps the legacy fallback in one place so future
 * call sites don't reinvent it.
 */
export declare function resolveSessionKind(ps: {
    kind?: string;
    delegateOf?: string;
}): "delegate" | "worker" | "reviewer";
//# sourceMappingURL=delegate-harness.d.ts.map