import type { StreamFn } from "@earendil-works/pi-agent-core";
import type { Message } from "@earendil-works/pi-ai";
import { type AgentSessionEvent, type ExtensionFactory, type ModelRegistry as ModelRegistryType, type ToolDefinition } from "@earendil-works/pi-coding-agent";
import { type TailLogEntry } from "./viewport/events.js";
export interface ForgePersona {
    name: string;
    description: string;
    model?: string;
    tools?: string[];
    systemPrompt: string;
    filePath: string;
}
export interface UsageStats {
    input: number;
    output: number;
    cacheRead: number;
    cacheWrite: number;
    cost: number;
    contextTokens: number;
    turns: number;
}
export interface SubagentResult {
    exitCode: 0 | 1;
    messages: Message[];
    usage: UsageStats;
    stopReason?: string;
    errorMessage?: string;
    model?: string;
    provider?: string;
    /**
     * Absolute path of the auto-exported subagent transcript
     * (.forge/transcripts/<entity>/<ISO>__<entity>__<phase>.json). Unset only
     * when the export failed (non-fatal). Orchestrators thread this into the
     * phase-end event's `subagentTranscriptPath` and the transcript archive.
     */
    subagentTranscriptPath?: string;
}
export interface RunSubagentOptions {
    persona: ForgePersona;
    task: string;
    cwd?: string;
    signal?: AbortSignal;
    /**
     * Forge root directory. When provided, FORGE_ROOT is set in the subagent's
     * process environment so that $FORGE_ROOT in workflow tool paths resolves
     * correctly. Without this, subagent bash calls to store-cli etc. will fail
     * with "Cannot find module '/tools/store-cli.cjs'".
     */
    forgeRoot?: string;
    /**
     * Per-phase model routing (Plan 16 Slice 2). When set, forge-cli calls
     * `session.agent.setModel(registry.find(provider, model))` after session
     * creation. If the model isn't in the registry, or setModel throws, the
     * phase falls through to inherit pi's current model — no crash.
     *
     * result.model / result.provider always reflect what the stream returned
     * (not this field) — IL10 emit path reads runtime telemetry, not the request.
     */
    requestedModel?: {
        provider: string;
        model: string;
    };
    /**
     * FORGE-BUG-001 followup. The host pi session's ModelRegistry — the only
     * registry that contains extension-registered providers (e.g.
     * `ollama-cloud`). When omitted, runForgeSubagent builds a fresh
     * `ModelRegistry.create(AuthStorage.create())` which only sees the
     * baseline providers wired up at process start, and every per-persona
     * `setModel` call will MISS — silently falling back to pi's current
     * model (typically Anthropic). Callers running inside an extension
     * command context MUST pass `ctx.modelRegistry` here.
     *
     * The same registry is used both for `registry.find(...)` (so the
     * routing decision sees the right providers) and as the subagent
     * session's registry (so the spawned agent can resolve the model id).
     */
    modelRegistry?: ModelRegistryType;
    onEvent?: (event: AgentSessionEvent) => void;
    /**
     * Optional tag included in the auto-exported transcript filename for
     * greppability — e.g. `${taskId}__${phaseRole}`. Stripped to a safe
     * filename slug. See forge-cli#8.
     */
    exportTag?: string;
    /**
     * LIVE reference to the viewport observer's tail-line record
     * (observer.state.tailLog). Read at export time — both on the normal
     * path and in the process-exit hook — and persisted next to the phase
     * transcript as `<transcript-base>.tail.jsonl` so transcript replay
     * shows the exact lines the dashboard rendered, even after /quit.
     */
    tailLog?: TailLogEntry[];
    /**
     * Optional prompt-cache session identifier forwarded to the underlying pi
     * Agent (which forwards it to the LLM provider as a cache namespace key).
     *
     * Forge sets this to a sprint-scoped value (e.g. `forge:FORGE-S21`) so
     * that every persona spawned within a sprint shares a cache prefix on
     * Anthropic (system prompt + persona + skills + tools all stay warm) and
     * a stable `prompt_cache_key` on OpenAI. Combined with
     * `PI_CACHE_RETENTION=long` (defaulted in bin/forge.ts), this captures
     * the majority of cacheable mass across a ~10-minute phase and the gaps
     * between phases.
     *
     * When omitted, no sessionId is set and providers fall back to their
     * default cache behaviour (Anthropic: implicit prefix match within the
     * stream; OpenAI: in-memory, request-scoped).
     */
    cacheSessionId?: string;
    /**
     * Test-only seam: when set, replaces the underlying pi Agent's `streamFn`
     * after `createAgentSession`. The agent loop still executes real tool calls
     * — only the LLM provider is replaced with a scripted fake. Used by
     * `test/helpers/scripted-subagent.ts` to drive ceremony / pipeline tests
     * without mocking `forge-subagent` itself. See forge-cli#17.
     *
     * MUST be `undefined` in production code. Setting this in production code
     * would silently route a real subagent dispatch through a fake provider.
     */
    streamFn?: StreamFn;
    /**
     * Forge tool definitions to inject into the subagent session via
     * `createAgentSession({ customTools })`. When provided, the subagent can
     * call `forge_store`, `forge_store_describe`, etc. as named MCP tools
     * instead of shelling out to `store-cli.cjs` via bash.
     *
     * Use `getSubagentTools(forgeToolDefs, persona.name)` from forge-tools.ts
     * to build the appropriate subset for each persona.
     */
    customTools?: ToolDefinition[];
    /**
     * When true, suppress extension and skill discovery in the subagent session.
     * Default false — subagents inherit the parent's extensions (lean-ctx,
     * ollama-cloud, etc.) so they get the same tool surface.
     *
     * Test harnesses should set this to true to avoid loading globally-installed
     * extensions (which adds latency and creates environment-dependent behavior).
     */
    noExtensions?: boolean;
    /**
     * Extension factories to inject into the subagent session via
     * DefaultResourceLoader({ extensionFactories }). When provided, the
     * factories are registered on the subagent's ExtensionRunner.
     * Used by Mechanism E (T09) to wire the Forge-aware compaction handler
     * into specific subagent sessions.
     *
     * These factories fire even when noExtensions=true — explicitly-passed
     * factories are separate from globally-discovered extensions and are not
     * suppressed by the noExtensions flag. This matches the pattern proven in
     * spike-r-cg3 (T08) and is the correct production behavior: callers that
     * want the governor's compaction handler can pass it explicitly regardless
     * of the global extension discovery flag.
     *
     * Best-effort: factory errors must be caught by the factory itself
     * (pi requirement). Callers must not pass factories that can throw at
     * registration time.
     */
    extensionFactories?: ExtensionFactory[];
}
/**
 * Load a Forge persona from `.forge/personas/<name>.md`.
 *
 * Frontmatter (optional, all keys may be missing):
 *   description: short role summary
 *   model: pi model id (default: project default)
 *   tools: comma-separated tool list (default: all coding tools)
 *
 * If frontmatter absent, name derives from filename and the entire file body
 * is used as the system prompt.
 */
export declare function loadForgePersona(name: string, cwd: string): ForgePersona;
/**
 * Spawn a Forge subagent in-process via pi SDK and run a task to completion.
 *
 * Returns a SubagentResult after `session.prompt()` resolves. If `signal`
 * fires, the session is aborted and the call returns with exitCode=1 and
 * stopReason="aborted".
 *
 * Usage events are aggregated from `turn_end` (per-turn assistant message
 * usage). Total contextTokens are sourced from the latest turn.
 */
export declare function runForgeSubagent(opts: RunSubagentOptions): Promise<SubagentResult>;
interface WriteTranscriptOptions {
    cwd: string;
    persona: string;
    tag?: string;
    result: SubagentResult;
    startedAt: Date;
    /**
     * The task body sent to session.prompt(). Captured because it never
     * appears in result.messages (the capture records assistant/toolResult
     * only) — without it, transcript replay has no prompt to show.
     */
    prompt?: string;
}
/**
 * Format a Date as a compact UTC timestamp for transcript filenames.
 * Example: 2026-05-28T13:35:00.123Z → "20260528T133500Z" (no dashes,
 * colons, or fractional seconds). Lexicographic sort matches chronological
 * order, which is the property that makes `ls` show transcripts in run order.
 */
export declare function formatTranscriptTimestamp(d: Date): string;
/** Derive the subdirectory and filename from the export tag.
 *
 * exportTag format for tasks/bugs/sprints: "<entityId>__<role>"
 *   → dir  = <entityId>  (e.g. "FORGE-S21-T04")
 *   → file = <ISO>__<entityId>__<role>.json
 *
 * Untagged / partial tag: "general" as dir, "<ISO>__<persona>.json" filename.
 */
export declare function computeTranscriptPath(cwd: string, persona: string, tag: string | undefined, startedAt: Date): string;
export declare function writeSubagentTranscript(opts: WriteTranscriptOptions): string;
/** Extract the final assistant text from a SubagentResult. */
export declare function getFinalOutput(messages: Message[]): string;
export {};
