/**
 * Extension runner - executes extensions and manages their lifecycle.
 */
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { CredentialDisabledEvent, ImageContent, Model, ProviderResponseMetadata } from "@oh-my-pi/pi-ai";
import type { KeyId } from "@oh-my-pi/pi-tui";
import type { ModelRegistry } from "../../config/model-registry.js";
import type { Settings } from "../../config/settings.js";
import type { MemoryRuntimeContext } from "../../memory-backend/index.js";
import type { SessionManager } from "../../session/session-manager.js";
import type { BranchHandler, NavigateTreeHandler, NewSessionHandler } from "../session-handler-types.js";
import type { AfterProviderResponseEvent, AssistantThinkingRenderer, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, ContextEvent, Extension, ExtensionActions, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFlag, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, InputEvent, InputEventResult, MessageRenderer, RegisteredCommand, RegisteredTool, ResourcesDiscoverEvent, SessionBeforeBranchResult, SessionBeforeCompactResult, SessionBeforeSwitchResult, SessionBeforeTreeResult, SessionCompactingResult, SessionStopEvent, SessionStopEventResult, ToolCallEvent, ToolCallEventResult, ToolResultEvent, ToolResultEventResult, UserBashEvent, UserBashEventResult, UserPythonEvent, UserPythonEventResult } from "./types.js";
/** Combined result from all before_agent_start handlers */
interface BeforeAgentStartCombinedResult {
    messages?: NonNullable<BeforeAgentStartEventResult["message"]>[];
    systemPrompt?: string[];
}
export type ExtensionErrorListener = (error: ExtensionError) => void;
export declare const EXTENSION_HANDLER_TIMEOUT_MS = 30000;
export declare function testSetExtensionHandlerTimeoutMs(timeoutMs: number): void;
/**
 * Dedicated cap for `session_shutdown` handlers. The generic 30s budget is
 * appropriate for events extensions can observe (e.g. `session_start`,
 * `before_provider_request`), but `session_shutdown` is fire-and-forget
 * teardown — extensions receive no result and the user has already asked to
 * leave. A hung handler (e.g. an extension waiting on a stuck IPC pipe to a
 * companion app) MUST NOT hold Ctrl+C / `/exit` hostage for the full window.
 * See issue #2600.
 */
export declare const SESSION_SHUTDOWN_HANDLER_TIMEOUT_MS = 2000;
export declare function testSetSessionShutdownHandlerTimeoutMs(timeoutMs: number): void;
/**
 * Events handled by the generic emit() method.
 * Events with dedicated emitXxx() methods are excluded for stronger type safety.
 */
type RunnerEmitEvent = Exclude<ExtensionEvent, ToolCallEvent | ToolResultEvent | UserBashEvent | ContextEvent | BeforeProviderRequestEvent | AfterProviderResponseEvent | BeforeAgentStartEvent | ResourcesDiscoverEvent | InputEvent>;
type RunnerEmitResult<TEvent extends RunnerEmitEvent> = TEvent extends {
    type: "session_before_switch";
} ? SessionBeforeSwitchResult | undefined : TEvent extends {
    type: "session_before_branch";
} ? SessionBeforeBranchResult | undefined : TEvent extends {
    type: "session_before_compact";
} ? SessionBeforeCompactResult | undefined : TEvent extends {
    type: "session_before_tree";
} ? SessionBeforeTreeResult | undefined : TEvent extends {
    type: "session.compacting";
} ? SessionCompactingResult | undefined : TEvent extends {
    type: "session_stop";
} ? SessionStopEventResult | undefined : undefined;
export type { BranchHandler, NavigateTreeHandler, NewSessionHandler };
export type SwitchSessionHandler = (sessionPath: string) => Promise<{
    cancelled: boolean;
}>;
export type ShutdownHandler = () => void;
/**
 * Helper function to emit session_shutdown event to extensions.
 * Returns true if the event was emitted, false if there were no handlers.
 */
export declare function emitSessionShutdownEvent(extensionRunner: ExtensionRunner | undefined): Promise<boolean>;
export declare class ExtensionRunner {
    #private;
    private readonly extensions;
    private readonly runtime;
    private readonly cwd;
    private readonly sessionManager;
    private readonly modelRegistry;
    private readonly settings?;
    constructor(extensions: Extension[], runtime: ExtensionRuntime, cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry, getMemory?: () => MemoryRuntimeContext | undefined, settings?: Settings | undefined);
    initialize(actions: ExtensionActions, contextActions: ExtensionContextActions, commandContextActions?: ExtensionCommandContextActions, uiContext?: ExtensionUIContext): void;
    /**
     * Forward a `credential_disabled` event from `AuthStorage` to extension handlers.
     *
     * If {@link initialize} has not yet run, the event is buffered and replayed once
     * initialize wires the runtime/UI context. This matters because mode controllers
     * (interactive, RPC, ACP, print, subagent) call `initialize()` AFTER `createAgentSession`
     * returns, but `AuthStorage` can fire `credential_disabled` during startup model probes
     * inside `createAgentSession()`. Without deferral, extension handlers would observe
     * `hasUI=false`, an unset model, and no-op runtime actions on exactly the headline
     * "OAuth invalid_grant during startup" path the event was designed to surface.
     *
     * Always returns; never throws. Errors from handlers are routed through
     * {@link onError} via {@link emit}'s normal isolation.
     */
    emitCredentialDisabled(event: CredentialDisabledEvent): Promise<void>;
    emitSessionStop(event: Omit<SessionStopEvent, "type">): Promise<SessionStopEventResult | undefined>;
    getUIContext(): ExtensionUIContext;
    hasUI(): boolean;
    getExtensionPaths(): string[];
    /** Get all registered tools from all extensions. */
    getAllRegisteredTools(): RegisteredTool[];
    /**
     * Aggregate the registered CLI flags across a set of extensions (last write
     * wins on name collision). Static so callers that need the flag set before a
     * runner exists — e.g. the CLI resolving `@file`/flag args before session
     * creation — share this exact logic instead of duplicating it.
     */
    static aggregateFlags(extensions: readonly Extension[]): Map<string, ExtensionFlag>;
    getFlags(): Map<string, ExtensionFlag>;
    getFlagValues(): Map<string, boolean | string>;
    setFlagValue(name: string, value: boolean | string): void;
    getShortcuts(): Map<KeyId, ExtensionShortcut>;
    onError(listener: ExtensionErrorListener): () => void;
    emitError(error: ExtensionError): void;
    hasHandlers(eventType: string): boolean;
    getMessageRenderer(customType: string): MessageRenderer | undefined;
    getAssistantThinkingRenderers(): AssistantThinkingRenderer[];
    getRegisteredCommands(reserved?: ReadonlySet<string>): RegisteredCommand[];
    getCommandDiagnostics(): Array<{
        type: string;
        message: string;
        path: string;
    }>;
    getCommand(name: string): RegisteredCommand | undefined;
    createContext(): ExtensionContext;
    /**
     * Request a graceful shutdown. Called by extension tools and event handlers.
     */
    shutdown(): void;
    createCommandContext(): ExtensionCommandContext;
    emit<TEvent extends RunnerEmitEvent>(event: TEvent): Promise<RunnerEmitResult<TEvent>>;
    emitToolResult(event: ToolResultEvent): Promise<ToolResultEventResult | undefined>;
    /**
     * Emit a `tool_call` event to every subscribed extension before the tool executes.
     *
     * Each handler is bounded by `extensionHandlerTimeoutMs` (default 30s). This
     * matches the timeout policy already applied to `emitToolResult` and every
     * other handler routed through `#runHandlerWithTimeout`; without it a single
     * hung extension (unresolved `await`, network call with no timeout) would
     * park `ExtensionToolWrapper.execute` indefinitely and freeze tool
     * dispatch — see issue #3948.
     *
     * On-timeout policy: **fail-closed** (return `{ block: true }`). This is
     * symmetric with the existing error path below and safer for a
     * pre-execution gate — an unresponsive extension MUST NOT be treated as
     * silent consent to run the tool.
     */
    emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined>;
    emitUserBash(event: UserBashEvent): Promise<UserBashEventResult | undefined>;
    emitUserPython(event: UserPythonEvent): Promise<UserPythonEventResult | undefined>;
    private emitUserEvent;
    emitResourcesDiscover(cwd: string, reason: ResourcesDiscoverEvent["reason"]): Promise<{
        skillPaths: Array<{
            path: string;
            extensionPath: string;
        }>;
        promptPaths: Array<{
            path: string;
            extensionPath: string;
        }>;
        themePaths: Array<{
            path: string;
            extensionPath: string;
        }>;
    }>;
    /** Emit input event. Transforms chain, "handled" short-circuits. */
    emitInput(text: string, images: ImageContent[] | undefined, source: "interactive" | "rpc" | "extension"): Promise<InputEventResult>;
    emitContext(messages: AgentMessage[]): Promise<AgentMessage[]>;
    emitBeforeProviderRequest(payload: unknown): Promise<BeforeProviderRequestEventResult>;
    emitAfterProviderResponse(response: ProviderResponseMetadata, _model?: Model): Promise<void>;
    emitBeforeAgentStart(prompt: string, images: ImageContent[] | undefined, systemPrompt: string[]): Promise<BeforeAgentStartCombinedResult | undefined>;
}
