/**
 * OwlCoda Native Tool Dispatcher
 *
 * Routes tool_use blocks from the model to native tool implementations,
 * executes them, and returns tool_result blocks for the conversation.
 */
import { createBashTool } from './tools/bash.js';
import { createReadTool } from './tools/read.js';
import { createWriteTool } from './tools/write.js';
import { createEditTool } from './tools/edit.js';
import { createGlobTool } from './tools/glob.js';
import { createGrepTool } from './tools/grep.js';
import { createWebFetchTool } from './tools/web-fetch.js';
import { createWebSearchTool } from './tools/web-search.js';
import { createTodoWriteTool } from './tools/todo-write.js';
import { createAskUserQuestionTool } from './tools/ask-user.js';
import { createSleepTool } from './tools/sleep.js';
import { createEnterPlanModeTool } from './tools/enter-plan-mode.js';
import { createExitPlanModeTool } from './tools/exit-plan-mode.js';
import { createConfigTool } from './tools/config.js';
import { createNotebookEditTool } from './tools/notebook-edit.js';
import { createEnterWorktreeTool } from './tools/enter-worktree.js';
import { createExitWorktreeTool } from './tools/exit-worktree.js';
import { createTaskCreateTool } from './tools/task-create.js';
import { createTaskListTool } from './tools/task-list.js';
import { createTaskGetTool } from './tools/task-get.js';
import { createTaskUpdateTool } from './tools/task-update.js';
import { createTaskStopTool } from './tools/task-stop.js';
import { createTaskOutputTool } from './tools/task-output.js';
import { createTaskVerifyTool } from './tools/task-verify.js';
import { createSendMessageTool } from './tools/send-message.js';
import { createTeamCreateTool } from './tools/team-create.js';
import { createTeamDeleteTool } from './tools/team-delete.js';
import { createToolSearchTool } from './tools/tool-search.js';
import { createStructuredOutputTool } from './tools/structured-output.js';
import { createScheduleCronTool } from './tools/schedule-cron.js';
import { createRemoteTriggerTool } from './tools/remote-trigger.js';
import { createMCPTool } from './tools/mcp-tool.js';
import { createListMcpResourcesTool } from './tools/list-mcp-resources.js';
import { createReadMcpResourceTool } from './tools/read-mcp-resource.js';
import { createMcpAuthTool } from './tools/mcp-auth.js';
import { createSkillTool } from './tools/skill.js';
import { createLSPTool } from './tools/lsp.js';
import { getNativeLspProvider } from './tools/lsp-provider.js';
import { createPowerShellTool } from './tools/powershell.js';
import { createBriefTool } from './tools/brief.js';
import { createDeliveryAuditTool } from './tools/delivery-audit.js';
import { createProbePlanTool } from './tools/probe-plan.js';
import { createSkillRoutePreviewTool } from './tools/skill-route-preview.js';
import { createRunWorkspaceTool } from './tools/run-workspace.js';
import { createProjectMapTool } from './tools/project-map.js';
import { createArtifactVerifyTool } from './tools/artifact-verify.js';
import { applyToolFailurePolicy } from './tools/semantic-failure.js';
import { evaluateWriteGuard, ensureRunWorkspaceForStructuredTask, markTaskWriteScopeBlocked, recordBashArtifactProgress, recordToolExecutionProgress, recordWriteSuccess, } from './task-state.js';
import { writePlan, recordArtifact } from './run-workspace.js';
import { normalize, isAbsolute, resolve } from 'node:path';
export class ToolDispatcher {
    mcpManager;
    tools = new Map();
    constructor(mcpManager) {
        this.mcpManager = mcpManager;
        this.registerDefaults();
    }
    /** Register a tool. */
    register(tool) {
        this.tools.set(tool.name, tool);
    }
    /** Remove a registered tool by name. */
    unregister(name) {
        return this.tools.delete(name);
    }
    /** Get all registered tool names. */
    getToolNames() {
        return [...this.tools.keys()];
    }
    /** Check if a tool is registered. */
    has(name) {
        return this.tools.has(name);
    }
    /**
     * Resolve the description authored by the tool's factory. Returns
     * `undefined` when the tool isn't registered or didn't supply one.
     *
     * Wire-the-honest-text exists because `buildNativeToolDefs` and
     * `ToolSearch` previously hardcoded a `"Native ${name} tool"`
     * placeholder, which silently dropped honest stub-disclosure copy
     * (e.g. SendMessage's "There is no consumer wired up to drain these
     * queues" or McpAuth's "tokens are NOT validated"). The LLM never
     * saw what the maintainer wrote.
     */
    getToolDescription(name) {
        return this.tools.get(name)?.description;
    }
    /** Execute a single tool_use block. */
    async executeTool(block, context) {
        const tool = this.tools.get(block.name);
        if (!tool) {
            return {
                toolUseId: block.id,
                toolName: block.name,
                result: {
                    output: `Error: unknown tool "${block.name}"`,
                    isError: true,
                },
                durationMs: 0,
            };
        }
        const start = Date.now();
        try {
            const guardViolation = evaluateWriteGuard(block.name, block.input, context?.taskState);
            if (guardViolation) {
                if (context?.taskState) {
                    markTaskWriteScopeBlocked(context.taskState, guardViolation.message, guardViolation.attemptedPath);
                }
                return {
                    toolUseId: block.id,
                    toolName: block.name,
                    result: {
                        output: guardViolation.message,
                        isError: true,
                        metadata: {
                            taskGuardBlocked: true,
                            attemptedPath: guardViolation.attemptedPath,
                            allowedPaths: guardViolation.allowedPaths,
                        },
                    },
                    durationMs: Date.now() - start,
                };
            }
            const rawResult = await tool.execute(block.input, context);
            const result = applyToolFailurePolicy(block.name, block.input, rawResult);
            await recordBashArtifactProgress(context?.taskState, block.name, block.input, start);
            if (!result.isError) {
                recordWriteSuccess(context?.taskState, block.name, block.input, result.metadata);
                recordToolExecutionProgress(context?.taskState, block.name, block.input, result.metadata);
                await ensureRunWorkspaceForStructuredTask(context?.taskState, block.name, block.input, result.metadata);
                // B2: Mirror TodoWrite todos → plan.json when a RunWorkspace exists.
                const runWorkspaceB2 = context?.taskState?.run.runWorkspace;
                if (block.name === 'TodoWrite' && runWorkspaceB2) {
                    await mirrorTodosToRunWorkspace(block.input, runWorkspaceB2.runDir).catch(() => { });
                }
                // B3: Append write/edit/NotebookEdit artifacts within outputRoot → artifacts.json.
                const runWorkspaceB3 = context?.taskState?.run.runWorkspace;
                if (runWorkspaceB3 && (block.name === 'write' || block.name === 'edit' || block.name === 'NotebookEdit')) {
                    await recordWriteArtifact(block.name, result.metadata, runWorkspaceB3.runDir, runWorkspaceB3.outputRoot).catch(() => { });
                }
            }
            return {
                toolUseId: block.id,
                toolName: block.name,
                result,
                durationMs: Date.now() - start,
            };
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            return {
                toolUseId: block.id,
                toolName: block.name,
                result: { output: `Error: ${msg}`, isError: true },
                durationMs: Date.now() - start,
            };
        }
    }
    /** Execute all tool_use blocks (sequentially to avoid race conditions). */
    async executeAll(blocks, context) {
        const results = [];
        for (const block of blocks) {
            results.push(await this.executeTool(block, context));
        }
        return results;
    }
    /** Convert execution results to tool_result content blocks for the next message. */
    toContentBlocks(results) {
        return results.map((r) => ({
            type: 'tool_result',
            tool_use_id: r.toolUseId,
            content: r.result.output,
            is_error: r.result.isError,
        }));
    }
    registerDefaults() {
        // Shared plan-mode state
        const planState = { inPlanMode: false };
        // Shared worktree state
        const worktreeState = { inWorktree: false };
        this.register(createBashTool());
        this.register(createReadTool());
        this.register(createWriteTool());
        this.register(createEditTool());
        this.register(createGlobTool());
        this.register(createGrepTool());
        this.register(createWebFetchTool());
        this.register(createWebSearchTool());
        this.register(createTodoWriteTool());
        this.register(createAskUserQuestionTool());
        this.register(createSleepTool());
        this.register(createEnterPlanModeTool(planState));
        this.register(createExitPlanModeTool(planState));
        this.register(createConfigTool());
        this.register(createNotebookEditTool());
        this.register(createEnterWorktreeTool(worktreeState));
        this.register(createExitWorktreeTool(worktreeState));
        this.register(createTaskCreateTool());
        this.register(createTaskListTool());
        this.register(createTaskGetTool());
        this.register(createTaskUpdateTool());
        this.register(createTaskStopTool());
        this.register(createTaskOutputTool());
        this.register(createTaskVerifyTool());
        this.register(createSendMessageTool());
        this.register(createTeamCreateTool());
        this.register(createTeamDeleteTool());
        this.register(createToolSearchTool({
            getToolDescription: (name) => this.getToolDescription(name),
        }));
        this.register(createStructuredOutputTool());
        this.register(createScheduleCronTool());
        this.register(createRemoteTriggerTool());
        this.register(createMCPTool(this.mcpManager));
        this.register(createListMcpResourcesTool(this.mcpManager));
        this.register(createReadMcpResourceTool(this.mcpManager));
        this.register(createMcpAuthTool());
        this.register(createSkillTool());
        this.register(createLSPTool(getNativeLspProvider()));
        this.register(createPowerShellTool());
        this.register(createBriefTool());
        this.register(createDeliveryAuditTool());
        this.register(createSkillRoutePreviewTool());
        this.register(createRunWorkspaceTool());
        this.register(createProjectMapTool());
        this.register(createArtifactVerifyTool());
        // ProbePlan registers without a live-conversation accessor by default;
        // ink-repl re-registers it with one, same pattern as Config/Agent.
        this.register(createProbePlanTool());
    }
}
/**
 * B2: Mirror TodoWrite todos to plan.json in the active RunWorkspace.
 * Mapping: todo.content → step.title; todo.status → step.status;
 * todo.activeForm → step.activeForm. Single-direction mirror only.
 */
async function mirrorTodosToRunWorkspace(input, runDir) {
    const todos = input['todos'];
    if (!Array.isArray(todos))
        return;
    const steps = todos.map((todo, index) => ({
        id: `todo-${index + 1}`,
        title: typeof todo['content'] === 'string' ? todo['content'] : String(todo['content'] ?? ''),
        status: typeof todo['status'] === 'string' ? todo['status'] : 'pending',
        ...(typeof todo['activeForm'] === 'string' && todo['activeForm'] !== todo['content']
            ? { activeForm: todo['activeForm'] }
            : {}),
    }));
    await writePlan(runDir, {
        version: 1,
        source: 'TodoWrite',
        updatedAt: new Date().toISOString(),
        steps,
    });
}
/**
 * B3: Record a write/edit/NotebookEdit artifact into artifacts.json.
 * Only records paths within outputRoot (isWithinRunRoot guard).
 * Silently returns if path is not in outputRoot or metadata is missing.
 */
async function recordWriteArtifact(toolName, metadata, runDir, outputRoot) {
    if (!metadata)
        return;
    const rawPath = toolName === 'NotebookEdit'
        ? metadata['notebook_path']
        : metadata['path'];
    if (typeof rawPath !== 'string' || !rawPath)
        return;
    const artifactPath = isAbsolute(rawPath) ? resolve(rawPath) : resolve(rawPath);
    const normalizedOutputRoot = normalize(outputRoot);
    const normalizedArtifact = normalize(artifactPath);
    // Only record artifacts within outputRoot
    if (normalizedArtifact !== normalizedOutputRoot && !normalizedArtifact.startsWith(`${normalizedOutputRoot}/`))
        return;
    const isFinalDir = normalizedArtifact.startsWith(normalize(`${outputRoot}/final`));
    await recordArtifact(runDir, {
        path: artifactPath,
        origin: toolName,
        participatesInFinal: isFinalDir,
    });
}
//# sourceMappingURL=dispatch.js.map