/**
 * Extension runner - executes extensions and manages their lifecycle.
 */
import { theme } from "../../modes/interactive/theme/theme.js";
// Extension shortcuts compete with canonical keybinding ids from keybindings.json.
// Only editor-global shortcuts are reserved here. Picker-specific bindings are not.
const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
    "app.interrupt",
    "app.clear",
    "app.exit",
    "app.suspend",
    "app.thinking.cycle",
    "app.model.cycleForward",
    "app.model.cycleBackward",
    "app.model.select",
    "app.tools.expand",
    "app.thinking.toggle",
    "app.editor.external",
    "app.message.copy",
    "app.message.followUp",
    "tui.input.submit",
    "tui.select.confirm",
    "tui.select.cancel",
    "tui.input.copy",
    "tui.editor.deleteToLineEnd",
];
const buildBuiltinKeybindings = (resolvedKeybindings) => {
    const builtinKeybindings = {};
    for (const [keybinding, keys] of Object.entries(resolvedKeybindings)) {
        if (keys === undefined)
            continue;
        const keyList = Array.isArray(keys) ? keys : [keys];
        const restrictOverride = RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS.includes(keybinding);
        for (const key of keyList) {
            const normalizedKey = key.toLowerCase();
            // If multiple actions bind the same key, the reserved action wins so extensions
            // remain blocked by reserved shortcuts regardless of iteration order.
            const existing = builtinKeybindings[normalizedKey];
            if (existing?.restrictOverride && !restrictOverride)
                continue;
            builtinKeybindings[normalizedKey] = {
                keybinding,
                restrictOverride,
            };
        }
    }
    return builtinKeybindings;
};
/**
 * Helper function to emit session_shutdown event to extensions.
 * Returns true if the event was emitted, false if there were no handlers.
 */
export async function emitSessionShutdownEvent(extensionRunner, event) {
    if (extensionRunner.hasHandlers("session_shutdown")) {
        await extensionRunner.emit(event);
        return true;
    }
    return false;
}
export async function emitProjectTrustEvent(extensionsResult, event, ctx) {
    const errors = [];
    for (const ext of extensionsResult.extensions) {
        // A single extension may register multiple handlers for the same event.
        // The first project_trust handler that returns yes/no wins; undecided falls through.
        const handlers = ext.handlers.get("project_trust");
        if (!handlers || handlers.length === 0)
            continue;
        for (const handler of handlers) {
            try {
                const handlerResult = (await handler(event, ctx));
                if (handlerResult.trusted === "undecided") {
                    continue;
                }
                return { result: handlerResult, errors };
            }
            catch (error) {
                errors.push({
                    extensionPath: ext.path,
                    event: event.type,
                    error: error instanceof Error ? error.message : String(error),
                    stack: error instanceof Error ? error.stack : undefined,
                });
            }
        }
    }
    return { errors };
}
const noOpUIContext = {
    select: async () => undefined,
    confirm: async () => false,
    input: async () => undefined,
    notify: () => { },
    onTerminalInput: () => () => { },
    setStatus: () => { },
    setWorkingMessage: () => { },
    setWorkingVisible: () => { },
    setWorkingIndicator: () => { },
    setHiddenThinkingLabel: () => { },
    setWidget: () => { },
    setFooter: () => { },
    setHeader: () => { },
    setTitle: () => { },
    custom: async () => undefined,
    pasteToEditor: () => { },
    setEditorText: () => { },
    getEditorText: () => "",
    editor: async () => undefined,
    addAutocompleteProvider: () => { },
    setEditorComponent: () => { },
    getEditorComponent: () => undefined,
    get theme() {
        return theme;
    },
    getAllThemes: () => [],
    getTheme: () => undefined,
    setTheme: (_theme) => ({ success: false, error: "UI not available" }),
    getToolsExpanded: () => false,
    setToolsExpanded: () => { },
};
export class ExtensionRunner {
    extensions;
    runtime;
    uiContext;
    mode = "print";
    cwd;
    sessionManager;
    modelRegistry;
    errorListeners = new Set();
    getModel = () => undefined;
    getScopedModels = () => [];
    isIdleFn = () => true;
    isProjectTrustedFn = () => true;
    getSignalFn = () => undefined;
    waitForIdleFn = async () => { };
    abortFn = () => { };
    hasPendingMessagesFn = () => false;
    getContextUsageFn = () => undefined;
    compactFn = () => { };
    getSystemPromptFn = () => "";
    getSystemPromptOptionsFn = () => ({ cwd: this.cwd });
    newSessionHandler = async () => ({ cancelled: false });
    forkHandler = async () => ({ cancelled: false });
    navigateTreeHandler = async () => ({ cancelled: false });
    switchSessionHandler = async () => ({ cancelled: false });
    reloadHandler = async () => { };
    shutdownHandler = () => { };
    shortcutDiagnostics = [];
    commandDiagnostics = [];
    staleMessage;
    constructor(extensions, runtime, cwd, sessionManager, modelRegistry) {
        this.extensions = extensions;
        this.runtime = runtime;
        this.uiContext = noOpUIContext;
        this.cwd = cwd;
        this.sessionManager = sessionManager;
        this.modelRegistry = modelRegistry;
    }
    bindCore(actions, contextActions, providerActions) {
        // Copy actions into the shared runtime (all extension APIs reference this)
        this.runtime.sendMessage = actions.sendMessage;
        this.runtime.sendUserMessage = actions.sendUserMessage;
        this.runtime.appendEntry = actions.appendEntry;
        this.runtime.setSessionName = actions.setSessionName;
        this.runtime.getSessionName = actions.getSessionName;
        this.runtime.setLabel = actions.setLabel;
        this.runtime.getActiveTools = actions.getActiveTools;
        this.runtime.getAllTools = actions.getAllTools;
        this.runtime.setActiveTools = actions.setActiveTools;
        this.runtime.refreshTools = actions.refreshTools;
        this.runtime.getCommands = actions.getCommands;
        this.runtime.setModel = actions.setModel;
        this.runtime.getThinkingLevel = actions.getThinkingLevel;
        this.runtime.setThinkingLevel = actions.setThinkingLevel;
        // Context actions (required)
        this.getModel = contextActions.getModel;
        this.getScopedModels = contextActions.getScopedModels;
        this.isIdleFn = contextActions.isIdle;
        this.isProjectTrustedFn = contextActions.isProjectTrusted;
        this.getSignalFn = contextActions.getSignal;
        this.abortFn = contextActions.abort;
        this.hasPendingMessagesFn = contextActions.hasPendingMessages;
        this.shutdownHandler = contextActions.shutdown;
        this.getContextUsageFn = contextActions.getContextUsage;
        this.compactFn = contextActions.compact;
        this.getSystemPromptFn = contextActions.getSystemPrompt;
        this.getSystemPromptOptionsFn = contextActions.getSystemPromptOptions ?? (() => ({ cwd: this.cwd }));
        // Flush provider registrations queued during extension loading
        for (const { name, config, extensionPath } of this.runtime.pendingProviderRegistrations) {
            try {
                if (providerActions?.registerProvider) {
                    providerActions.registerProvider(name, config);
                }
                else {
                    this.modelRegistry.registerProvider(name, config);
                }
            }
            catch (err) {
                this.emitError({
                    extensionPath,
                    event: "register_provider",
                    error: err instanceof Error ? err.message : String(err),
                    stack: err instanceof Error ? err.stack : undefined,
                });
            }
        }
        this.runtime.pendingProviderRegistrations = [];
        for (const { provider, extensionPath } of this.runtime.pendingNativeProviderRegistrations) {
            try {
                if (providerActions?.registerNativeProvider) {
                    providerActions.registerNativeProvider(provider);
                }
                else {
                    this.modelRegistry.registerProvider(provider);
                }
            }
            catch (err) {
                this.emitError({
                    extensionPath,
                    event: "register_provider",
                    error: err instanceof Error ? err.message : String(err),
                    stack: err instanceof Error ? err.stack : undefined,
                });
            }
        }
        this.runtime.pendingNativeProviderRegistrations = [];
        // From this point on, provider registration/unregistration takes effect immediately
        // without requiring a /reload.
        this.runtime.registerProvider = (name, config) => {
            if (providerActions?.registerProvider) {
                providerActions.registerProvider(name, config);
                return;
            }
            this.modelRegistry.registerProvider(name, config);
        };
        this.runtime.registerNativeProvider = (provider) => {
            if (providerActions?.registerNativeProvider) {
                providerActions.registerNativeProvider(provider);
                return;
            }
            this.modelRegistry.registerProvider(provider);
        };
        this.runtime.unregisterProvider = (name) => {
            if (providerActions?.unregisterProvider) {
                providerActions.unregisterProvider(name);
                return;
            }
            this.modelRegistry.unregisterProvider(name);
        };
    }
    bindCommandContext(actions) {
        if (actions) {
            this.waitForIdleFn = actions.waitForIdle;
            this.newSessionHandler = actions.newSession;
            this.forkHandler = actions.fork;
            this.navigateTreeHandler = actions.navigateTree;
            this.switchSessionHandler = actions.switchSession;
            this.reloadHandler = actions.reload;
            return;
        }
        this.waitForIdleFn = async () => { };
        this.newSessionHandler = async () => ({ cancelled: false });
        this.forkHandler = async () => ({ cancelled: false });
        this.navigateTreeHandler = async () => ({ cancelled: false });
        this.switchSessionHandler = async () => ({ cancelled: false });
        this.reloadHandler = async () => { };
    }
    setUIContext(uiContext, mode = "print") {
        this.uiContext = uiContext ?? noOpUIContext;
        this.mode = mode;
    }
    getUIContext() {
        return this.uiContext;
    }
    hasUI() {
        return this.uiContext !== noOpUIContext;
    }
    getExtensionPaths() {
        return this.extensions.map((e) => e.path);
    }
    /** Get all registered tools from all extensions (first registration per name wins). */
    getAllRegisteredTools() {
        const toolsByName = new Map();
        for (const ext of this.extensions) {
            for (const tool of ext.tools.values()) {
                if (!toolsByName.has(tool.definition.name)) {
                    toolsByName.set(tool.definition.name, tool);
                }
            }
        }
        return Array.from(toolsByName.values());
    }
    /** Get a tool definition by name. Returns undefined if not found. */
    getToolDefinition(toolName) {
        for (const ext of this.extensions) {
            const tool = ext.tools.get(toolName);
            if (tool) {
                return tool.definition;
            }
        }
        return undefined;
    }
    getFlags() {
        const allFlags = new Map();
        for (const ext of this.extensions) {
            for (const [name, flag] of ext.flags) {
                if (!allFlags.has(name)) {
                    allFlags.set(name, flag);
                }
            }
        }
        return allFlags;
    }
    setFlagValue(name, value) {
        this.runtime.flagValues.set(name, value);
    }
    getFlagValues() {
        return new Map(this.runtime.flagValues);
    }
    getShortcuts(resolvedKeybindings) {
        this.shortcutDiagnostics = [];
        const builtinKeybindings = buildBuiltinKeybindings(resolvedKeybindings);
        const extensionShortcuts = new Map();
        const addDiagnostic = (message, extensionPath) => {
            this.shortcutDiagnostics.push({ type: "warning", message, path: extensionPath });
            if (!this.hasUI()) {
                console.warn(message);
            }
        };
        for (const ext of this.extensions) {
            for (const [key, shortcut] of ext.shortcuts) {
                const normalizedKey = key.toLowerCase();
                const builtInKeybinding = builtinKeybindings[normalizedKey];
                if (builtInKeybinding?.restrictOverride === true) {
                    addDiagnostic(`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`, shortcut.extensionPath);
                    continue;
                }
                if (builtInKeybinding?.restrictOverride === false) {
                    addDiagnostic(`Extension shortcut conflict: '${key}' is built-in shortcut for ${builtInKeybinding.keybinding} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`, shortcut.extensionPath);
                }
                const existingExtensionShortcut = extensionShortcuts.get(normalizedKey);
                if (existingExtensionShortcut) {
                    addDiagnostic(`Extension shortcut conflict: '${key}' registered by both ${existingExtensionShortcut.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`, shortcut.extensionPath);
                }
                extensionShortcuts.set(normalizedKey, shortcut);
            }
        }
        return extensionShortcuts;
    }
    getShortcutDiagnostics() {
        return this.shortcutDiagnostics;
    }
    invalidate(message = "This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().") {
        if (!this.staleMessage) {
            this.staleMessage = message;
            this.runtime.invalidate(message);
        }
    }
    assertActive() {
        if (this.staleMessage) {
            throw new Error(this.staleMessage);
        }
    }
    onError(listener) {
        this.errorListeners.add(listener);
        return () => this.errorListeners.delete(listener);
    }
    emitError(error) {
        for (const listener of this.errorListeners) {
            listener(error);
        }
    }
    hasHandlers(eventType) {
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get(eventType);
            if (handlers && handlers.length > 0) {
                return true;
            }
        }
        return false;
    }
    getMessageRenderer(customType) {
        for (const ext of this.extensions) {
            const renderer = ext.messageRenderers.get(customType);
            if (renderer) {
                return renderer;
            }
        }
        return undefined;
    }
    getEntryRenderer(customType) {
        for (const ext of this.extensions) {
            const renderer = ext.entryRenderers?.get(customType);
            if (renderer) {
                return renderer;
            }
        }
        return undefined;
    }
    resolveRegisteredCommands() {
        const commands = [];
        const counts = new Map();
        for (const ext of this.extensions) {
            for (const command of ext.commands.values()) {
                commands.push(command);
                counts.set(command.name, (counts.get(command.name) ?? 0) + 1);
            }
        }
        const seen = new Map();
        const takenInvocationNames = new Set();
        return commands.map((command) => {
            const occurrence = (seen.get(command.name) ?? 0) + 1;
            seen.set(command.name, occurrence);
            let invocationName = (counts.get(command.name) ?? 0) > 1 ? `${command.name}:${occurrence}` : command.name;
            if (takenInvocationNames.has(invocationName)) {
                let suffix = occurrence;
                do {
                    suffix++;
                    invocationName = `${command.name}:${suffix}`;
                } while (takenInvocationNames.has(invocationName));
            }
            takenInvocationNames.add(invocationName);
            return {
                ...command,
                invocationName,
            };
        });
    }
    getModelRegistry() {
        return this.modelRegistry;
    }
    getRegisteredCommands() {
        this.commandDiagnostics = [];
        return this.resolveRegisteredCommands();
    }
    getCommandDiagnostics() {
        return this.commandDiagnostics;
    }
    getCommand(name) {
        return this.resolveRegisteredCommands().find((command) => command.invocationName === name);
    }
    /**
     * Request a graceful shutdown. Called by extension tools and event handlers.
     * The actual shutdown behavior is provided by the mode via bindExtensions().
     */
    shutdown() {
        this.shutdownHandler();
    }
    getActiveTools() {
        this.assertActive();
        return this.runtime.getActiveTools();
    }
    /**
     * Create an ExtensionContext for use in event handlers and tool execution.
     * Context values are resolved at call time, so changes via bindCore/bindUI are reflected.
     */
    createContext() {
        const runner = this;
        const getModel = this.getModel;
        const getScopedModels = this.getScopedModels;
        return {
            get ui() {
                runner.assertActive();
                return runner.uiContext;
            },
            get mode() {
                runner.assertActive();
                return runner.mode;
            },
            get hasUI() {
                runner.assertActive();
                return runner.hasUI();
            },
            get cwd() {
                runner.assertActive();
                return runner.cwd;
            },
            get sessionManager() {
                runner.assertActive();
                return runner.sessionManager;
            },
            get modelRegistry() {
                runner.assertActive();
                return runner.modelRegistry;
            },
            get model() {
                runner.assertActive();
                return getModel();
            },
            get scopedModels() {
                runner.assertActive();
                return getScopedModels();
            },
            get thinkingLevel() {
                runner.assertActive();
                return runner.runtime.getThinkingLevel();
            },
            isIdle: () => {
                runner.assertActive();
                return runner.isIdleFn();
            },
            isProjectTrusted: () => {
                runner.assertActive();
                return runner.isProjectTrustedFn();
            },
            get signal() {
                runner.assertActive();
                return runner.getSignalFn();
            },
            abort: () => {
                runner.assertActive();
                runner.abortFn();
            },
            hasPendingMessages: () => {
                runner.assertActive();
                return runner.hasPendingMessagesFn();
            },
            shutdown: () => {
                runner.assertActive();
                runner.shutdownHandler();
            },
            getContextUsage: () => {
                runner.assertActive();
                return runner.getContextUsageFn();
            },
            compact: (options) => {
                runner.assertActive();
                runner.compactFn(options);
            },
            getSystemPrompt: () => {
                runner.assertActive();
                return runner.getSystemPromptFn();
            },
        };
    }
    createCommandContext() {
        // Use property descriptors instead of object spread so the guarded getters from
        // createContext() stay lazy. A spread would eagerly read them once and freeze the
        // old values into the returned object, bypassing stale-instance checks.
        const context = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext()));
        context.getSystemPromptOptions = () => {
            this.assertActive();
            return this.getSystemPromptOptionsFn();
        };
        context.waitForIdle = () => {
            this.assertActive();
            return this.waitForIdleFn();
        };
        context.newSession = (options) => {
            this.assertActive();
            return this.newSessionHandler(options);
        };
        context.fork = (entryId, options) => {
            this.assertActive();
            return this.forkHandler(entryId, options);
        };
        context.navigateTree = (targetId, options) => {
            this.assertActive();
            return this.navigateTreeHandler(targetId, options);
        };
        context.switchSession = (sessionPath, options) => {
            this.assertActive();
            return this.switchSessionHandler(sessionPath, options);
        };
        context.reload = () => {
            this.assertActive();
            return this.reloadHandler();
        };
        return context;
    }
    isSessionBeforeEvent(event) {
        return (event.type === "session_before_switch" ||
            event.type === "session_before_fork" ||
            event.type === "session_before_compact" ||
            event.type === "session_before_tree");
    }
    async emit(event) {
        const ctx = this.createContext();
        let result;
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get(event.type);
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                try {
                    const handlerResult = await handler(event, ctx);
                    if (this.isSessionBeforeEvent(event) && handlerResult) {
                        result = handlerResult;
                        if (result.cancel) {
                            return result;
                        }
                    }
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    const stack = err instanceof Error ? err.stack : undefined;
                    this.emitError({
                        extensionPath: ext.path,
                        event: event.type,
                        error: message,
                        stack,
                    });
                }
            }
        }
        return result;
    }
    async emitMessageEnd(event) {
        const ctx = this.createContext();
        let currentMessage = event.message;
        let modified = false;
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get("message_end");
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                try {
                    const currentEvent = { ...event, message: currentMessage };
                    const handlerResult = (await handler(currentEvent, ctx));
                    if (!handlerResult?.message)
                        continue;
                    if (handlerResult.message.role !== currentMessage.role) {
                        this.emitError({
                            extensionPath: ext.path,
                            event: "message_end",
                            error: "message_end handlers must return a message with the same role",
                        });
                        continue;
                    }
                    currentMessage = handlerResult.message;
                    modified = true;
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    const stack = err instanceof Error ? err.stack : undefined;
                    this.emitError({
                        extensionPath: ext.path,
                        event: "message_end",
                        error: message,
                        stack,
                    });
                }
            }
        }
        return modified ? currentMessage : undefined;
    }
    async emitToolResult(event) {
        const ctx = this.createContext();
        const currentEvent = { ...event };
        let modified = false;
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get("tool_result");
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                try {
                    const handlerResult = (await handler(currentEvent, ctx));
                    if (!handlerResult)
                        continue;
                    if (handlerResult.content !== undefined) {
                        currentEvent.content = handlerResult.content;
                        modified = true;
                    }
                    if (handlerResult.details !== undefined) {
                        currentEvent.details = handlerResult.details;
                        modified = true;
                    }
                    if (handlerResult.isError !== undefined) {
                        currentEvent.isError = handlerResult.isError;
                        modified = true;
                    }
                    if (handlerResult.usage !== undefined) {
                        currentEvent.usage = handlerResult.usage;
                        modified = true;
                    }
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    const stack = err instanceof Error ? err.stack : undefined;
                    this.emitError({
                        extensionPath: ext.path,
                        event: "tool_result",
                        error: message,
                        stack,
                    });
                }
            }
        }
        if (!modified) {
            return undefined;
        }
        return {
            content: currentEvent.content,
            details: currentEvent.details,
            isError: currentEvent.isError,
            usage: currentEvent.usage,
        };
    }
    async emitToolCall(event) {
        const ctx = this.createContext();
        let result;
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get("tool_call");
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                const handlerResult = await handler(event, ctx);
                if (handlerResult) {
                    result = handlerResult;
                    if (result.block) {
                        return result;
                    }
                }
            }
        }
        return result;
    }
    async emitUserBash(event) {
        const ctx = this.createContext();
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get("user_bash");
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                try {
                    const handlerResult = await handler(event, ctx);
                    if (handlerResult) {
                        return handlerResult;
                    }
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    const stack = err instanceof Error ? err.stack : undefined;
                    this.emitError({
                        extensionPath: ext.path,
                        event: "user_bash",
                        error: message,
                        stack,
                    });
                }
            }
        }
        return undefined;
    }
    async emitContext(messages) {
        const ctx = this.createContext();
        let currentMessages = structuredClone(messages);
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get("context");
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                try {
                    const event = { type: "context", messages: currentMessages };
                    const handlerResult = await handler(event, ctx);
                    if (handlerResult && handlerResult.messages) {
                        currentMessages = handlerResult.messages;
                    }
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    const stack = err instanceof Error ? err.stack : undefined;
                    this.emitError({
                        extensionPath: ext.path,
                        event: "context",
                        error: message,
                        stack,
                    });
                }
            }
        }
        return currentMessages;
    }
    async emitBeforeProviderRequest(payload) {
        const ctx = this.createContext();
        let currentPayload = payload;
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get("before_provider_request");
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                try {
                    const event = {
                        type: "before_provider_request",
                        payload: currentPayload,
                    };
                    const handlerResult = await handler(event, ctx);
                    if (handlerResult !== undefined) {
                        currentPayload = handlerResult;
                    }
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    const stack = err instanceof Error ? err.stack : undefined;
                    this.emitError({
                        extensionPath: ext.path,
                        event: "before_provider_request",
                        error: message,
                        stack,
                    });
                }
            }
        }
        return currentPayload;
    }
    async emitBeforeProviderHeaders(headers) {
        const ctx = this.createContext();
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get("before_provider_headers");
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                try {
                    // Handlers mutate `headers` in place; the return value is ignored.
                    const event = {
                        type: "before_provider_headers",
                        headers,
                    };
                    await handler(event, ctx);
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    const stack = err instanceof Error ? err.stack : undefined;
                    this.emitError({
                        extensionPath: ext.path,
                        event: "before_provider_headers",
                        error: message,
                        stack,
                    });
                }
            }
        }
        return headers;
    }
    async emitBeforeAgentStart(prompt, images, systemPrompt, systemPromptOptions) {
        let currentSystemPrompt = systemPrompt;
        const ctx = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext()));
        ctx.getSystemPrompt = () => {
            this.assertActive();
            return currentSystemPrompt;
        };
        const messages = [];
        let systemPromptModified = false;
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get("before_agent_start");
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                try {
                    const event = {
                        type: "before_agent_start",
                        prompt,
                        images,
                        systemPrompt: currentSystemPrompt,
                        systemPromptOptions,
                    };
                    const handlerResult = await handler(event, ctx);
                    if (handlerResult) {
                        const result = handlerResult;
                        if (result.message) {
                            messages.push(result.message);
                        }
                        if (result.systemPrompt !== undefined) {
                            currentSystemPrompt = result.systemPrompt;
                            systemPromptModified = true;
                        }
                    }
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    const stack = err instanceof Error ? err.stack : undefined;
                    this.emitError({
                        extensionPath: ext.path,
                        event: "before_agent_start",
                        error: message,
                        stack,
                    });
                }
            }
        }
        if (messages.length > 0 || systemPromptModified) {
            return {
                messages: messages.length > 0 ? messages : undefined,
                systemPrompt: systemPromptModified ? currentSystemPrompt : undefined,
            };
        }
        return undefined;
    }
    async emitResourcesDiscover(cwd, reason) {
        const ctx = this.createContext();
        const skillPaths = [];
        const promptPaths = [];
        const themePaths = [];
        for (const ext of this.extensions) {
            const handlers = ext.handlers.get("resources_discover");
            if (!handlers || handlers.length === 0)
                continue;
            for (const handler of handlers) {
                try {
                    const event = { type: "resources_discover", cwd, reason };
                    const handlerResult = await handler(event, ctx);
                    const result = handlerResult;
                    if (result?.skillPaths?.length) {
                        skillPaths.push(...result.skillPaths.map((path) => ({ path, extensionPath: ext.path })));
                    }
                    if (result?.promptPaths?.length) {
                        promptPaths.push(...result.promptPaths.map((path) => ({ path, extensionPath: ext.path })));
                    }
                    if (result?.themePaths?.length) {
                        themePaths.push(...result.themePaths.map((path) => ({ path, extensionPath: ext.path })));
                    }
                }
                catch (err) {
                    const message = err instanceof Error ? err.message : String(err);
                    const stack = err instanceof Error ? err.stack : undefined;
                    this.emitError({
                        extensionPath: ext.path,
                        event: "resources_discover",
                        error: message,
                        stack,
                    });
                }
            }
        }
        return { skillPaths, promptPaths, themePaths };
    }
    /** Emit input event. Transforms chain, "handled" short-circuits. */
    async emitInput(text, images, source, streamingBehavior) {
        const ctx = this.createContext();
        let currentText = text;
        let currentImages = images;
        for (const ext of this.extensions) {
            for (const handler of ext.handlers.get("input") ?? []) {
                try {
                    const event = {
                        type: "input",
                        text: currentText,
                        images: currentImages,
                        source,
                        streamingBehavior,
                    };
                    const result = (await handler(event, ctx));
                    if (result?.action === "handled")
                        return result;
                    if (result?.action === "transform") {
                        currentText = result.text;
                        currentImages = result.images ?? currentImages;
                    }
                }
                catch (err) {
                    this.emitError({
                        extensionPath: ext.path,
                        event: "input",
                        error: err instanceof Error ? err.message : String(err),
                        stack: err instanceof Error ? err.stack : undefined,
                    });
                }
            }
        }
        return currentText !== text || currentImages !== images
            ? { action: "transform", text: currentText, images: currentImages }
            : { action: "continue" };
    }
}
//# sourceMappingURL=runner.js.map